root/bin/ksh/emacs.c
/*      $NetBSD: emacs.c,v 1.38 2018/05/08 16:37:59 kamil Exp $ */

/*
 *  Emacs-like command line editing and history
 *
 *  created by Ron Natalie at BRL
 *  modified by Doug Kingston, Doug Gwyn, and Lou Salkind
 *  adapted to PD ksh by Eric Gisin
 */
#include <sys/cdefs.h>

#ifndef lint
__RCSID("$NetBSD: emacs.c,v 1.38 2018/05/08 16:37:59 kamil Exp $");
#endif

#include "config.h"
#ifdef EMACS

#include <sys/stat.h>
#include <ctype.h>
#include <locale.h>
#include <stdbool.h>

#include "sh.h"
#include "ksh_dir.h"
#include "edit.h"

static  Area    aedit;
#define AEDIT   &aedit          /* area for kill ring and macro defns */

#undef CTRL                     /* _BSD brain damage */
#define CTRL(x)         ((x) == '?' ? 0x7F : (x) & 0x1F)        /* ASCII */
#define UNCTRL(x)       ((x) == 0x7F ? '?' : (x) | 0x40)        /* ASCII */
#define META(x)         ((x) & 0x7f)
#define ISMETA(x)       (Flag(FEMACSUSEMETA) && ((x) & 0x80))


/* values returned by keyboard functions */
#define KSTD    0
#define KEOL    1               /* ^M, ^J */
#define KINTR   2               /* ^G, ^C */

struct  x_ftab  {
        int             (*xf_func) ARGS((int c));
        const char      *xf_name;
        short           xf_flags;
};

/* index into struct x_ftab x_ftab[] - small is good */
typedef unsigned char Findex;

struct x_defbindings {
        Findex          xdb_func;       /* XFUNC_* */
        unsigned char   xdb_tab;
        unsigned char   xdb_char;
};

#define XF_ARG          1       /* command takes number prefix */
#define XF_NOBIND       2       /* not allowed to bind to function */
#define XF_PREFIX       4       /* function sets prefix */

/* Separator for completion */
#define is_cfs(c)       (c == ' ' || c == '\t' || c == '"' || c == '\'')
#define is_mfs(c)       (!(isalnum((unsigned char)c) || c == '_' || c == '$'))  /* Separator for motion */

# define CHARMASK       0xFF            /* 8-bit character mask */
# define X_NTABS        3               /* normal, meta1, meta2 */
#define X_TABSZ         (CHARMASK+1)    /* size of keydef tables etc */

/* Arguments for do_complete()
 * 0 = enumerate  M-= complete as much as possible and then list
 * 1 = complete   M-Esc
 * 2 = list       M-?
 */
typedef enum { CT_LIST,         /* list the possible completions */
                 CT_COMPLETE,   /* complete to longest prefix */
                 CT_COMPLIST    /* complete and then list (if non-exact) */
        } Comp_type;

/* { from 4.9 edit.h */
/*
 * The following are used for my horizontal scrolling stuff
 */
static char   *xbuf;            /* beg input buffer */
static char   *xend;            /* end input buffer */
static char    *xcp;            /* current position */
static char    *xep;            /* current end */
static char    *xbp;            /* start of visible portion of input buffer */
static char    *xlp;            /* last char visible on screen */
static int      x_adj_ok;
/*
 * we use x_adj_done so that functions can tell
 * whether x_adjust() has been called while they are active.
 */
static int      x_adj_done;

static int      xx_cols;
static int      x_col;
static int      x_displen;
static int      x_arg;          /* general purpose arg */
static int      x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */

static int      xlp_valid;
/* end from 4.9 edit.h } */

static  int     x_prefix1 = CTRL('['), x_prefix2 = CTRL('X');
static  char   **x_histp;       /* history position */
static  int     x_nextcmd;      /* for newline-and-next */
static  char    *xmp;           /* mark pointer */
static  Findex   x_last_command;
static  Findex (*x_tab)[X_TABSZ];       /* key definition */
static  char    *(*x_atab)[X_TABSZ];    /* macro definitions */
static  unsigned char   x_bound[(X_TABSZ * X_NTABS + 7) / 8];
#define KILLSIZE        20
static  char    *killstack[KILLSIZE];
static  int     killsp, killtp;
static  int     x_curprefix;
static  char    *macroptr;
static  int     prompt_trunc;
static  int     prompt_skip;

static int      x_ins       ARGS((char *cp));
static void     x_delete    ARGS((int nc, int push));
static int      x_bword     ARGS((void));
static int      x_fword     ARGS((void));
static void     x_goto      ARGS((char *cp));
static void     x_bs        ARGS((int c));
static int      x_size_str  ARGS((char *cp));
static int      x_size      ARGS((int c));
static void     x_zots      ARGS((char *str));
static void     x_zotc      ARGS((int c));
static void     x_load_hist ARGS((char **hp));
static int      x_search    ARGS((char *pat, int sameline, int offset));
static int      x_match     ARGS((char *str, char *pat));
static void     x_redraw    ARGS((int limit));
static void     x_push      ARGS((int nchars));
static char *   x_mapin     ARGS((const char *cp, Area *area));
static char *   x_mapout    ARGS((int c));
static void     x_print     ARGS((int prefix, int key));
static void     x_adjust    ARGS((void));
static void     x_e_ungetc  ARGS((int c));
static int      x_e_getc    ARGS((void));
static void     x_e_putc    ARGS((int c));
static void     x_e_puts    ARGS((const char *s));
static int      x_comment   ARGS((int c));
static int      x_fold_case ARGS((int c));
static char     *x_lastcp ARGS((void));
static void     do_complete ARGS((int flags, Comp_type type));
static int      x_emacs_putbuf  ARGS((const char *s, size_t len));


/* The lines between START-FUNC-TAB .. END-FUNC-TAB are run through a
 * script (emacs-gen.sh) that generates emacs.out which contains:
 *      - function declarations for x_* functions
 *      - defines of the form XFUNC_<name> where <name> is function
 *        name, sans leading x_.
 * Note that the script treats #ifdef and { 0, 0, 0} specially - use with
 * caution.
 */
#include "emacs.out"
static const struct x_ftab x_ftab[] = {
/* @START-FUNC-TAB@ */
        { x_abort,              "abort",                        0 },
        { x_beg_hist,           "beginning-of-history",         0 },
        { x_comp_comm,          "complete-command",             0 },
        { x_comp_file,          "complete-file",                0 },
        { x_complete,           "complete",                     0 },
        { x_del_back,           "delete-char-backward",         XF_ARG },
        { x_del_bword,          "delete-word-backward",         XF_ARG },
        { x_del_char,           "delete-char-forward",          XF_ARG },
        { x_del_fword,          "delete-word-forward",          XF_ARG },
        { x_del_line,           "kill-line",                    0 },
        { x_draw_line,          "redraw",                       0 },
        { x_end_hist,           "end-of-history",               0 },
        { x_end_of_text,        "eot",                          0 },
        { x_enumerate,          "list",                         0 },
        { x_eot_del,            "eot-or-delete",                XF_ARG },
        { x_error,              "error",                        0 },
        { x_goto_hist,          "goto-history",                 XF_ARG },
        { x_ins_string,         "macro-string",                 XF_NOBIND },
        { x_insert,             "auto-insert",                  XF_ARG },
        { x_kill,               "kill-to-eol",                  XF_ARG },
        { x_kill_region,        "kill-region",                  0 },
        { x_list_comm,          "list-command",                 0 },
        { x_list_file,          "list-file",                    0 },
        { x_literal,            "quote",                        0 },
        { x_meta1,              "prefix-1",                     XF_PREFIX },
        { x_meta2,              "prefix-2",                     XF_PREFIX },
        { x_meta_yank,          "yank-pop",                     0 },
        { x_mv_back,            "backward-char",                XF_ARG },
        { x_mv_begin,           "beginning-of-line",            0 },
        { x_mv_bword,           "backward-word",                XF_ARG },
        { x_mv_end,             "end-of-line",                  0 },
        { x_mv_forw,            "forward-char",                 XF_ARG },
        { x_mv_fword,           "forward-word",                 XF_ARG },
        { x_newline,            "newline",                      0 },
        { x_next_com,           "down-history",                 XF_ARG },
        { x_nl_next_com,        "newline-and-next",             0 },
        { x_noop,               "no-op",                        0 },
        { x_prev_com,           "up-history",                   XF_ARG },
        { x_prev_histword,      "prev-hist-word",               XF_ARG },
        { x_search_char_forw,   "search-character-forward",     XF_ARG },
        { x_search_char_back,   "search-character-backward",    XF_ARG },
        { x_search_hist,        "search-history",               0 },
        { x_set_mark,           "set-mark-command",             0 },
        { x_stuff,              "stuff",                        0 },
        { x_stuffreset,         "stuff-reset",                  0 },
        { x_transpose,          "transpose-chars",              0 },
        { x_version,            "version",                      0 },
        { x_xchg_point_mark,    "exchange-point-and-mark",      0 },
        { x_yank,               "yank",                         0 },
        { x_comp_list,          "complete-list",                0 },
        { x_expand,             "expand-file",                  0 },
        { x_fold_capitalize,    "capitalize-word",              XF_ARG },
        { x_fold_lower,         "downcase-word",                XF_ARG },
        { x_fold_upper,         "upcase-word",                  XF_ARG },
        { x_set_arg,            "set-arg",                      XF_NOBIND },
        { x_comment,            "comment",                      0 },
#ifdef SILLY
        { x_game_of_life,       "play-game-of-life",            0 },
#else
        { 0, 0, 0 },
#endif
#ifdef DEBUG
        { x_debug_info,         "debug-info",                   0 },
#else
        { 0, 0, 0 },
        { 0, 0, 0 },
#endif
/* @END-FUNC-TAB@ */
    };

static  struct x_defbindings const x_defbindings[] = {
        { XFUNC_del_back,               0, CTRL('?') },
        { XFUNC_del_bword,              1, CTRL('?') },
        { XFUNC_eot_del,                0, CTRL('D') },
        { XFUNC_del_back,               0, CTRL('H') },
        { XFUNC_del_bword,              1, CTRL('H') },
        { XFUNC_del_bword,              1,      'h'  },
        { XFUNC_mv_bword,               1,      'b'  },
        { XFUNC_mv_fword,               1,      'f'  },
        { XFUNC_del_fword,              1,      'd'  },
        { XFUNC_mv_back,                0, CTRL('B') },
        { XFUNC_mv_forw,                0, CTRL('F') },
        { XFUNC_search_char_forw,       0, CTRL(']') },
        { XFUNC_search_char_back,       1, CTRL(']') },
        { XFUNC_newline,                0, CTRL('M') },
        { XFUNC_newline,                0, CTRL('J') },
        { XFUNC_end_of_text,            0, CTRL('_') },
        { XFUNC_abort,                  0, CTRL('G') },
        { XFUNC_prev_com,               0, CTRL('P') },
        { XFUNC_next_com,               0, CTRL('N') },
        { XFUNC_nl_next_com,            0, CTRL('O') },
        { XFUNC_search_hist,            0, CTRL('R') },
        { XFUNC_beg_hist,               1,      '<'  },
        { XFUNC_end_hist,               1,      '>'  },
        { XFUNC_goto_hist,              1,      'g'  },
        { XFUNC_mv_end,                 0, CTRL('E') },
        { XFUNC_mv_begin,               0, CTRL('A') },
        { XFUNC_draw_line,              0, CTRL('L') },
        { XFUNC_meta1,                  0, CTRL('[') },
        { XFUNC_meta2,                  0, CTRL('X') },
        { XFUNC_kill,                   0, CTRL('K') },
        { XFUNC_yank,                   0, CTRL('Y') },
        { XFUNC_meta_yank,              1,      'y'  },
        { XFUNC_literal,                0, CTRL('^') },
        { XFUNC_comment,                1,      '#'  },
#if defined(BRL) && defined(TIOCSTI)
        { XFUNC_stuff,                  0, CTRL('T') },
#else
        { XFUNC_transpose,              0, CTRL('T') },
#endif
        { XFUNC_complete,               1, CTRL('[') },
        { XFUNC_comp_list,              0, CTRL('I') },
        { XFUNC_comp_list,              1,      '='  },
        { XFUNC_enumerate,              1,      '?'  },
        { XFUNC_expand,                 1,      '*'  },
        { XFUNC_comp_file,              1, CTRL('X') },
        { XFUNC_comp_comm,              2, CTRL('[') },
        { XFUNC_list_comm,              2,      '?'  },
        { XFUNC_list_file,              2, CTRL('Y') },
        { XFUNC_set_mark,               1,      ' '  },
        { XFUNC_kill_region,            0, CTRL('W') },
        { XFUNC_xchg_point_mark,        2, CTRL('X') },
        { XFUNC_version,                0, CTRL('V') },
#ifdef DEBUG
        { XFUNC_debug_info,             1, CTRL('H') },
#endif
        { XFUNC_prev_histword,          1,      '.'  },
        { XFUNC_prev_histword,          1,      '_'  },
        { XFUNC_set_arg,                1,      '0'  },
        { XFUNC_set_arg,                1,      '1'  },
        { XFUNC_set_arg,                1,      '2'  },
        { XFUNC_set_arg,                1,      '3'  },
        { XFUNC_set_arg,                1,      '4'  },
        { XFUNC_set_arg,                1,      '5'  },
        { XFUNC_set_arg,                1,      '6'  },
        { XFUNC_set_arg,                1,      '7'  },
        { XFUNC_set_arg,                1,      '8'  },
        { XFUNC_set_arg,                1,      '9'  },
        { XFUNC_fold_upper,             1,      'U'  },
        { XFUNC_fold_upper,             1,      'u'  },
        { XFUNC_fold_lower,             1,      'L'  },
        { XFUNC_fold_lower,             1,      'l'  },
        { XFUNC_fold_capitalize,        1,      'C'  },
        { XFUNC_fold_capitalize,        1,      'c'  },
        /* These for ansi arrow keys: arguablely shouldn't be here by
         * default, but its simpler/faster/smaller than using termcap
         * entries.
         */
        { XFUNC_meta2,                  1,      '['  },
        { XFUNC_meta2,                  1,      'O'  },
        { XFUNC_prev_com,               2,      'A'  },
        { XFUNC_next_com,               2,      'B'  },
        { XFUNC_mv_forw,                2,      'C'  },
        { XFUNC_mv_back,                2,      'D'  },
};

int
x_emacs(buf, len)
        char *buf;
        size_t len;
{
        int     c;
        const char *p;
        int     i;
        Findex  f;

        xbp = xbuf = buf; xend = buf + len;
        xlp = xcp = xep = buf;
        *xcp = 0;
        xlp_valid = true;
        xmp = NULL;
        x_curprefix = 0;
        macroptr = (char *) 0;
        x_histp = histptr + 1;
        x_last_command = XFUNC_error;

        xx_cols = x_cols;
        x_col = promptlen(prompt, &p);
        prompt_skip = p - prompt;
        prompt_trunc = x_col - (x_cols - 3 - MIN_EDIT_SPACE);
        if (prompt_trunc > 0)
                x_col -= prompt_trunc;
        else
                prompt_trunc = 0;
        x_adj_ok = 1;
        x_displen = xx_cols - 2 - x_col;
        x_adj_done = 0;

        pprompt(prompt, prompt_trunc);

        if (x_nextcmd >= 0) {
                int off = source->line - x_nextcmd;
                if (histptr - histlist >= off)
                        x_load_hist(histptr - off);
                x_nextcmd = -1;
        }

        while (1) {
                x_flush();
                if ((c = x_e_getc()) < 0)
                        return 0;

                if (ISMETA(c)) {
                        c = META(c);
                        x_curprefix = 1;
                }

                f = x_curprefix == -1 ? XFUNC_insert
                        : x_tab[x_curprefix][c&CHARMASK];

                if (!(x_ftab[f].xf_flags & XF_PREFIX)
                    && x_last_command != XFUNC_set_arg)
                {
                        x_arg = 1;
                        x_arg_defaulted = 1;
                }
                i = c | (x_curprefix << 8);
                x_curprefix = 0;
                switch (i = (*x_ftab[f].xf_func)(i))  {
                  case KSTD:
                        if (!(x_ftab[f].xf_flags & XF_PREFIX))
                                x_last_command = f;
                        break;
                  case KEOL:
                        i = xep - xbuf;
                        return i;
                  case KINTR:   /* special case for interrupt */
                        trapsig(SIGINT);
                        x_mode(false);
                        unwind(LSHELL);
                }
        }
}

static int
x_insert(c)
        int c;
{
        char    str[2];

        /*
         *  Should allow tab and control chars.
         */
        if (c == 0)  {
                x_e_putc(BEL);
                return KSTD;
        }
        str[0] = c;
        str[1] = '\0';
        while (x_arg--)
                x_ins(str);
        return KSTD;
}

static int
x_ins_string(c)
        int c;
{
        if (macroptr)   {
                x_e_putc(BEL);
                return KSTD;
        }
        macroptr = x_atab[c>>8][c & CHARMASK];
        if (macroptr && !*macroptr) {
                /* XXX bell? */
                macroptr = (char *) 0;
        }
        return KSTD;
}

static int x_do_ins(const char *cp, int len);

static int
x_do_ins(cp, len)
        const char *cp;
        int len;
{
        if (xep+len >= xend) {
                x_e_putc(BEL);
                return -1;
        }

        memmove(xcp+len, xcp, xep - xcp + 1);
        memmove(xcp, cp, len);
        xcp += len;
        xep += len;
        return 0;
}

static int
x_ins(s)
        char    *s;
{
        char *cp = xcp;
        int     adj = x_adj_done;

        if (x_do_ins(s, strlen(s)) < 0)
                return -1;
        /*
         * x_zots() may result in a call to x_adjust()
         * we want xcp to reflect the new position.
         */
        xlp_valid = false;
        x_lastcp();
        x_adj_ok = (xcp >= xlp);
        x_zots(cp);
        if (adj == x_adj_done)  /* has x_adjust() been called? */
        {
          /* no */
          for (cp = xlp; cp > xcp; )
            x_bs(*--cp);
        }

        x_adj_ok = 1;
        return 0;
}

/*
 * this is used for x_escape() in do_complete()
 */
static int
x_emacs_putbuf(s, len)
        const char *s;
        size_t len;
{
        int rval;

        if ((rval = x_do_ins(s, len)) != 0)
                return (rval);
        return (rval);
}

static int
x_del_back(c)
        int c;
{
        int col = xcp - xbuf;

        if (col == 0)  {
                x_e_putc(BEL);
                return KSTD;
        }
        if (x_arg > col)
                x_arg = col;
        x_goto(xcp - x_arg);
        x_delete(x_arg, false);
        return KSTD;
}

static int
x_del_char(c)
        int c;
{
        int nleft = xep - xcp;

        if (!nleft) {
                x_e_putc(BEL);
                return KSTD;
        }
        if (x_arg > nleft)
                x_arg = nleft;
        x_delete(x_arg, false);
        return KSTD;
}

/* Delete nc chars to the right of the cursor (including cursor position) */
static void
x_delete(nc, push)
        int nc;
        int push;
{
        int     i,j;
        char    *cp;
        
        if (nc == 0)
                return;
        if (xmp != NULL && xmp > xcp) {
                if (xcp + nc > xmp)
                        xmp = xcp;
                else
                        xmp -= nc;
        }

        /*
         * This lets us yank a word we have deleted.
         */
        if (push)
                x_push(nc);

        xep -= nc;
        cp = xcp;
        j = 0;
        i = nc;
        while (i--)  {
                j += x_size(*cp++);
        }
        memmove(xcp, xcp+nc, xep - xcp + 1);    /* Copies the null */
        x_adj_ok = 0;                   /* don't redraw */
        x_zots(xcp);
        /*
         * if we are already filling the line,
         * there is no need to ' ','\b'.
         * But if we must, make sure we do the minimum.
         */
        if ((i = x_displen) > 0)
        {
          j = (j < i) ? j : i;
          i = j;
          while (i--)
            x_e_putc(' ');
          i = j;
          while (i--)
            x_e_putc('\b');
        }
        /*x_goto(xcp);*/
        x_adj_ok = 1;
        xlp_valid = false;
        for (cp = x_lastcp(); cp > xcp; )
                x_bs(*--cp);

        return; 
}

static int
x_del_bword(c)
        int c;
{
        x_delete(x_bword(), true);
        return KSTD;
}

static int
x_mv_bword(c)
        int c;
{
        (void)x_bword();
        return KSTD;
}

static int
x_mv_fword(c)
        int c;
{
        x_goto(xcp + x_fword());
        return KSTD;
}

static int
x_del_fword(c)
        int c;
{
        x_delete(x_fword(), true);
        return KSTD;
}

static int
x_bword()
{
        int     nc = 0;
        char *cp = xcp;

        if (cp == xbuf)  {
                x_e_putc(BEL);
                return 0;
        }
        while (x_arg--)
        {
          while (cp != xbuf && is_mfs(cp[-1]))
          {
            cp--;
            nc++;
          }
          while (cp != xbuf && !is_mfs(cp[-1]))
          {
            cp--;
            nc++;
          }
        }
        x_goto(cp);
        return nc;
}

static int
x_fword()
{
        int     nc = 0;
        char    *cp = xcp;

        if (cp == xep)  {
                x_e_putc(BEL);
                return 0;
        }
        while (x_arg--)
        {
          while (cp != xep && is_mfs(*cp))
          {
            cp++;
            nc++;
          }
          while (cp != xep && !is_mfs(*cp))
          {
            cp++;
            nc++;
          }
        }
        return nc;
}

static void
x_goto(cp)
        char *cp;
{
  if (cp < xbp || cp >= (xbp + x_displen))
  {
    /* we are heading off screen */
    xcp = cp;
    x_adjust();
  }
  else
  {
    if (cp < xcp)               /* move back */
    {
      while (cp < xcp)
        x_bs(*--xcp);
    }
    else
    {
      if (cp > xcp)             /* move forward */
      {
        while (cp > xcp)
          x_zotc(*xcp++);
      }
    }
  }
}

static void
x_bs(c)
        int c;
{
        int i;
        i = x_size(c);
        while (i--)
                x_e_putc('\b');
}

static int
x_size_str(cp)
        char *cp;
{
        int size = 0;
        while (*cp)
                size += x_size(*cp++);
        return size;
}

static int
x_size(c)
        int c;
{
        if (c=='\t')
                return 4;       /* Kludge, tabs are always four spaces. */
        if (iscntrl((unsigned char)c))          /* control char */
                return 2;
        return 1;
}

static void
x_zots(str)
        char *str;
{
  int   adj = x_adj_done;

  x_lastcp();
  while (*str && str < xlp && adj == x_adj_done)
    x_zotc(*str++);
}

static void
x_zotc(c)
        int c;
{
        if (c == '\t')  {
                /*  Kludge, tabs are always four spaces.  */
                x_e_puts("    ");
        } else if (iscntrl((unsigned char)c))  {
                x_e_putc('^');
                x_e_putc(UNCTRL(c));
        } else
                x_e_putc(c);
}

static int
x_mv_back(c)
        int c;
{
        int col = xcp - xbuf;

        if (col == 0)  {
                x_e_putc(BEL);
                return KSTD;
        }
        if (x_arg > col)
                x_arg = col;
        x_goto(xcp - x_arg);
        return KSTD;
}

static int
x_mv_forw(c)
        int c;
{
        int nleft = xep - xcp;

        if (!nleft) {
                x_e_putc(BEL);
                return KSTD;
        }
        if (x_arg > nleft)
                x_arg = nleft;
        x_goto(xcp + x_arg);
        return KSTD;
}

static int
x_search_char_forw(c)
        int c;
{
        char *cp = xcp;

        *xep = '\0';
        c = x_e_getc();
        while (x_arg--) {
            if (c < 0
               || ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL
                   && (cp = strchr(xbuf, c)) == NULL))
            {
                    x_e_putc(BEL);
                    return KSTD;
            }
        }
        x_goto(cp);
        return KSTD;
}

static int
x_search_char_back(c)
        int c;
{
        char *cp = xcp, *p;

        c = x_e_getc();
        for (; x_arg--; cp = p)
                for (p = cp; ; ) {
                        if (p-- == xbuf)
                                p = xep;
                        if (c < 0 || p == cp) {
                                x_e_putc(BEL);
                                return KSTD;
                        }
                        if (*p == c)
                                break;
                }
        x_goto(cp);
        return KSTD;
}

static int
x_newline(c)
        int c;
{
        x_e_putc('\r');
        x_e_putc('\n');
        x_flush();
        *xep++ = '\n';
        return KEOL;
}

static int
x_end_of_text(c)
        int c;
{
        return KEOL;
}

static int x_beg_hist(c) int c; { x_load_hist(histlist); return KSTD;}

static int x_end_hist(c) int c; { x_load_hist(histptr); return KSTD;}

static int x_prev_com(c) int c; { x_load_hist(x_histp - x_arg); return KSTD;}

static int x_next_com(c) int c; { x_load_hist(x_histp + x_arg); return KSTD;}
 
/* Goto a particular history number obtained from argument.
 * If no argument is given history 1 is probably not what you
 * want so we'll simply go to the oldest one.
 */
static int
x_goto_hist(c)
        int c;
{
        if (x_arg_defaulted)
                x_load_hist(histlist);
        else
                x_load_hist(histptr + x_arg - source->line);
        return KSTD;
}

static void
x_load_hist(hp)
        char **hp;
{
        int     oldsize;

        if (hp < histlist || hp > histptr) {
                x_e_putc(BEL);
                return;
        }
        x_histp = hp;
        oldsize = x_size_str(xbuf);
        strlcpy(xbuf, *hp, xend - xbuf);
        xbp = xbuf;
        xep = xcp = xbuf + strlen(xbuf);
        xlp_valid = false;
        if (xep > x_lastcp())
          x_goto(xep);
        else
          x_redraw(oldsize);
}

static int
x_nl_next_com(c)
        int     c;
{
        x_nextcmd = source->line - (histptr - x_histp) + 1;
        return (x_newline(c));
}

static int
x_eot_del(c)
        int     c;
{
        if (xep == xbuf && x_arg_defaulted)
                return (x_end_of_text(c));
        else
                return (x_del_char(c));
}

/* reverse incremental history search */
static int
x_search_hist(c)
        int c;
{
        int offset = -1;        /* offset of match in xbuf, else -1 */
        char pat [256+1];       /* pattern buffer */
        char *p = pat;
        Findex f;

        *p = '\0';
        while (1) {
                if (offset < 0) {
                        x_e_puts("\nI-search: ");
                        x_e_puts(pat);
                }
                x_flush();
                if ((c = x_e_getc()) < 0)
                        return KSTD;
                f = x_tab[0][c&CHARMASK];
                if (c == CTRL('['))
                        break;
                else if (f == XFUNC_search_hist)
                        offset = x_search(pat, 0, offset);
                else if (f == XFUNC_del_back) {
                        if (p == pat) {
                                offset = -1;
                                break;
                        }
                        if (p > pat)
                                *--p = '\0';
                        if (p == pat)
                                offset = -1;
                        else
                                offset = x_search(pat, 1, offset);
                        continue;
                } else if (f == XFUNC_insert) {
                        /* add char to pattern */
                        /* overflow check... */
                        if (p >= &pat[sizeof(pat) - 1]) {
                                x_e_putc(BEL);
                                continue;
                        }
                        *p++ = c, *p = '\0';
                        if (offset >= 0) {
                                /* already have partial match */
                                offset = x_match(xbuf, pat);
                                if (offset >= 0) {
                                        x_goto(xbuf + offset + (p - pat) - (*pat == '^'));
                                        continue;
                                }
                        }
                        offset = x_search(pat, 0, offset);
                } else { /* other command */
                        x_e_ungetc(c);
                        break;
                }
        }
        if (offset < 0)
                x_redraw(-1);
        return KSTD;
}

/* search backward from current line */
static int
x_search(pat, sameline, offset)
        char *pat;
        int sameline;
        int offset;
{
        char **hp;
        int i;

        for (hp = x_histp - (sameline ? 0 : 1) ; hp >= histlist; --hp) {
                i = x_match(*hp, pat);
                if (i >= 0) {
                        if (offset < 0)
                                x_e_putc('\n');
                        x_load_hist(hp);
                        x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
                        return i;
                }
        }
        x_e_putc(BEL);
        x_histp = histptr;
        return -1;
}

/* return position of first match of pattern in string, else -1 */
static int
x_match(str, pat)
        char *str, *pat;
{
        if (*pat == '^') {
                return (strncmp(str, pat+1, strlen(pat+1)) == 0) ? 0 : -1;
        } else {
                char *q = strstr(str, pat);
                return (q == NULL) ? -1 : q - str;
        }
}

static int
x_del_line(c)
        int c;
{
        int     i, j;

        *xep = 0;
        i = xep - xbuf;
        j = x_size_str(xbuf);
        xcp = xbuf;
        x_push(i);
        xlp = xbp = xep = xbuf;
        xlp_valid = true;
        *xcp = 0;
        xmp = NULL;
        x_redraw(j);
        return KSTD;
}

static int
x_mv_end(c)
        int c;
{
        x_goto(xep);
        return KSTD;
}

static int
x_mv_begin(c)
        int c;
{
        x_goto(xbuf);
        return KSTD;
}

static int
x_draw_line(c)
        int c;
{
        x_redraw(-1);
        return KSTD;

}

/* Redraw (part of) the line.  If limit is < 0, the everything is redrawn
 * on a NEW line, otherwise limit is the screen column up to which needs
 * redrawing.
 */
static void
x_redraw(limit)
  int limit;
{
        int     i, j;
        char    *cp;
        
        x_adj_ok = 0;
        if (limit == -1)
                x_e_putc('\n');
        else
                x_e_putc('\r');
        x_flush();
        if (xbp == xbuf)
        {
          pprompt(prompt + prompt_skip, 0);
          x_col = promptlen(prompt, (const char **) 0);
        }
        x_displen = xx_cols - 2 - x_col;
        xlp_valid = false;
        cp = x_lastcp();
        x_zots(xbp);
        if (xbp != xbuf || xep > xlp)
          limit = xx_cols;
        if (limit >= 0)
        {
          if (xep > xlp)
            i = 0;                      /* we fill the line */
          else
            i = limit - (xlp - xbp);

          for (j = 0; j < i && x_col < (xx_cols - 2); j++)
            x_e_putc(' ');
          i = ' ';
          if (xep > xlp)                /* more off screen */
          {
            if (xbp > xbuf)
              i = '*';
            else
              i = '>';
          }
          else
            if (xbp > xbuf)
              i = '<';
          x_e_putc(i);
          j++;
          while (j--)
            x_e_putc('\b');
        }
        for (cp = xlp; cp > xcp; )
          x_bs(*--cp);
        x_adj_ok = 1;
        D__(x_flush();)
        return;
}

static int
x_transpose(c)
        int c;
{
        char    tmp;

        /* What transpose is meant to do seems to be up for debate. This
         * is a general summary of the options; the text is abcd with the
         * upper case character or underscore indicating the cursor position:
         *     Who                      Before  After  Before   After
         *     at&t ksh in emacs mode:  abCd    abdC   abcd_    (bell)
         *     at&t ksh in gmacs mode:  abCd    baCd   abcd_    abdc_
         *     gnu emacs:               abCd    acbD   abcd_    abdc_
         * Pdksh currently goes with GNU behavior since I believe this is the
         * most common version of emacs, unless in gmacs mode, in which case
         * it does the at&t ksh gmacs mode.
         * This should really be broken up into 3 functions so users can bind
         * to the one they want.
         */
        if (xcp == xbuf) {
                x_e_putc(BEL);
                return KSTD;
        } else if (xcp == xep || Flag(FGMACS)) {
                if (xcp - xbuf == 1) {
                        x_e_putc(BEL);
                        return KSTD;
                }
                /* Gosling/Unipress emacs style: Swap two characters before the
                 * cursor, do not change cursor position
                 */
                x_bs(xcp[-1]);
                x_bs(xcp[-2]);
                x_zotc(xcp[-1]);
                x_zotc(xcp[-2]);
                tmp = xcp[-1];
                xcp[-1] = xcp[-2];
                xcp[-2] = tmp;
        } else {
                /* GNU emacs style: Swap the characters before and under the
                 * cursor, move cursor position along one.
                 */
                x_bs(xcp[-1]);
                x_zotc(xcp[0]);
                x_zotc(xcp[-1]);
                tmp = xcp[-1];
                xcp[-1] = xcp[0];
                xcp[0] = tmp;
                x_bs(xcp[0]);
                x_goto(xcp + 1);
        }
        return KSTD;
}

static int
x_literal(c)
        int c;
{
        x_curprefix = -1;
        return KSTD;
}

static int
x_meta1(c)
        int c;
{
        x_curprefix = 1;
        return KSTD;
}

static int
x_meta2(c)
        int c;
{
        x_curprefix = 2;
        return KSTD;
}

static int
x_kill(c)
        int c;
{
        int col = xcp - xbuf;
        int lastcol = xep - xbuf;
        int ndel;

        if (x_arg_defaulted)
                x_arg = lastcol;
        else if (x_arg > lastcol)
                x_arg = lastcol;
        ndel = x_arg - col;
        if (ndel < 0) {
                x_goto(xbuf + x_arg);
                ndel = -ndel;
        }
        x_delete(ndel, true);
        return KSTD;
}

static void
x_push(nchars)
        int nchars;
{
        char    *cp = str_nsave(xcp, nchars, AEDIT);
        if (killstack[killsp])
                afree((void *)killstack[killsp], AEDIT);
        killstack[killsp] = cp;
        killsp = (killsp + 1) % KILLSIZE;
}

static int
x_yank(c)
        int c;
{
        if (killsp == 0)
                killtp = KILLSIZE;
        else
                killtp = killsp;
        killtp--;
        if (killstack[killtp] == 0)  {
                x_e_puts("\nnothing to yank");
                x_redraw(-1);
                return KSTD;
        }
        xmp = xcp;
        x_ins(killstack[killtp]);
        return KSTD;
}

static int
x_meta_yank(c)
        int c;
{
        int     len;
        if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank)
            || killstack[killtp] == 0) {
                killtp = killsp;
                x_e_puts("\nyank something first");
                x_redraw(-1);
                return KSTD;
        }
        len = strlen(killstack[killtp]);
        x_goto(xcp - len);
        x_delete(len, false);
        do {
                if (killtp == 0)
                        killtp = KILLSIZE - 1;
                else
                        killtp--;
        } while (killstack[killtp] == 0);
        x_ins(killstack[killtp]);
        return KSTD;
}

static int
x_abort(c)
        int c;
{
        /* x_zotc(c); */
        xlp = xep = xcp = xbp = xbuf;
        xlp_valid = true;
        *xcp = 0;
        return KINTR;
}

static int
x_error(c)
        int c;
{
        x_e_putc(BEL);
        return KSTD;
}

static int
x_stuffreset(c)
        int c;
{
#ifdef TIOCSTI
        (void)x_stuff(c);
        return KINTR;
#else
        x_zotc(c);
        xlp = xcp = xep = xbp = xbuf;
        xlp_valid = true;
        *xcp = 0;
        x_redraw(-1);
        return KSTD;
#endif
}

static int
x_stuff(c)
        int c;
{
#if 0 || defined TIOCSTI
        char    ch = c;
        bool    savmode = x_mode(false);

        (void)ioctl(TTY, TIOCSTI, &ch);
        (void)x_mode(savmode);
        x_redraw(-1);
#endif
        return KSTD;
}

static char *
x_mapin(cp, area)
        const char *cp;
        Area *area;
{
        char *new, *op;

        op = new = str_save(cp, area);
        while (*cp)  {
                /* XXX -- should handle \^ escape? */
                if (*cp == '^')  {
                        cp++;
                        if (*cp >= '?') /* includes '?'; ASCII */
                                *op++ = CTRL(*cp);
                        else  {
                                *op++ = '^';
                                cp--;
                        }
                } else
                        *op++ = *cp;
                cp++;
        }
        *op = '\0';

        return new;
}

static char *
x_mapout(c)
        int c;
{
        static char buf[8];
        char *p = buf;

        if (iscntrl((unsigned char)c))  {
                *p++ = '^';
                *p++ = UNCTRL(c);
        } else
                *p++ = c;
        *p = 0;
        return buf;
}

static void
x_print(prefix, key)
        int prefix, key;
{
        if (prefix == 1)
                shprintf("%s", x_mapout(x_prefix1));
        if (prefix == 2)
                shprintf("%s", x_mapout(x_prefix2));

        shprintf("%s = ", x_mapout(key));
        if (x_tab[prefix][key] != XFUNC_ins_string)
                shprintf("%s\n", x_ftab[x_tab[prefix][key]].xf_name);
        else
                shprintf("'%s'\n", x_atab[prefix][key]);
}

int
x_bind(a1, a2, macro, list)
        const char *a1, *a2;
        int macro;              /* bind -m */
        int list;               /* bind -l */
{
        Findex f;
        int prefix, key;
        char *sp = NULL;
        char *m1, *m2;

        if (x_tab == NULL) {
                bi_errorf("cannot bind, not a tty");
                return 1;
        }

        /* List function names */
        if (list) {
                for (f = 0; f < NELEM(x_ftab); f++)
                        if (x_ftab[f].xf_name
                            && !(x_ftab[f].xf_flags & XF_NOBIND))
                                shprintf("%s\n", x_ftab[f].xf_name);
                return 0;
        }

        if (a1 == NULL) {
                for (prefix = 0; prefix < X_NTABS; prefix++)
                        for (key = 0; key < X_TABSZ; key++) {
                                f = x_tab[prefix][key];
                                if (f == XFUNC_insert || f == XFUNC_error
                                    || (macro && f != XFUNC_ins_string))
                                        continue;
                                x_print(prefix, key);
                        }
                return 0;
        }

        m2 = m1 = x_mapin(a1, ATEMP);
        prefix = key = 0;
        for (;; m1++) {
                key = *m1 & CHARMASK;
                if (x_tab[prefix][key] == XFUNC_meta1)
                        prefix = 1;
                else if (x_tab[prefix][key] == XFUNC_meta2)
                        prefix = 2;
                else
                        break;
        }
        afree(m2, ATEMP);

        if (a2 == NULL) {
                x_print(prefix, key);
                return 0;
        }

        if (*a2 == 0)
                f = XFUNC_insert;
        else if (!macro) {
                for (f = 0; f < NELEM(x_ftab); f++)
                        if (x_ftab[f].xf_name
                            && strcmp(x_ftab[f].xf_name, a2) == 0)
                                break;
                if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
                        bi_errorf("%s: no such function", a2);
                        return 1;
                }
#if 0           /* This breaks the bind commands that map arrow keys */
                if (f == XFUNC_meta1)
                        x_prefix1 = key;
                if (f == XFUNC_meta2)
                        x_prefix2 = key;
#endif /* 0 */
        } else {
                f = XFUNC_ins_string;
                sp = x_mapin(a2, AEDIT);
        }

        if (x_tab[prefix][key] == XFUNC_ins_string && x_atab[prefix][key])
                afree((void *)x_atab[prefix][key], AEDIT);
        x_tab[prefix][key] = f;
        x_atab[prefix][key] = sp;

        /* Track what the user has bound so x_emacs_keys() won't toast things */
        if (f == XFUNC_insert)
                x_bound[(prefix * X_TABSZ + key) / 8] &=
                        ~(1 << ((prefix * X_TABSZ + key) % 8));
        else
                x_bound[(prefix * X_TABSZ + key) / 8] |=
                        (1 << ((prefix * X_TABSZ + key) % 8));

        return 0;
}

void
x_init_emacs()
{
        size_t i;
        int j;
        char *locale;

        ainit(AEDIT);
        x_nextcmd = -1;

        x_tab = (Findex (*)[X_TABSZ]) alloc(sizeofN(*x_tab, X_NTABS), AEDIT);
        for (j = 0; j < X_TABSZ; j++)
                x_tab[0][j] = XFUNC_insert;
        for (i = 1; i < X_NTABS; i++)
                for (j = 0; j < X_TABSZ; j++)
                        x_tab[i][j] = XFUNC_error;
        for (i = 0; i < NELEM(x_defbindings); i++)
                x_tab[(unsigned char)x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char]
                        = x_defbindings[i].xdb_func;

        x_atab = (char *(*)[X_TABSZ]) alloc(sizeofN(*x_atab, X_NTABS), AEDIT);
        for (i = 1; i < X_NTABS; i++)
                for (j = 0; j < X_TABSZ; j++)
                        x_atab[i][j] = NULL;

        /* Determine if we can translate meta key or use 8-bit AscII 
         * XXX - It would be nice if there was a locale attribute to
         * determine if the locale is 7-bit or not.
         */
        locale = setlocale(LC_CTYPE, NULL);
        if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX"))
                Flag(FEMACSUSEMETA) = 0;
}

static void bind_if_not_bound(int p, int k, int func);

static void
bind_if_not_bound(p, k, func)
        int p, k;
        int func;
{
        /* Has user already bound this key?  If so, don't override it */
        if (x_bound[((p) * X_TABSZ + (k)) / 8]
            & (1 << (((p) * X_TABSZ + (k)) % 8)))
                return;

        x_tab[p][k] = func;
}

void
x_emacs_keys(ec)
        X_chars *ec;
{
        if (ec->erase >= 0) {
                bind_if_not_bound(0, ec->erase, XFUNC_del_back);
                bind_if_not_bound(1, ec->erase, XFUNC_del_bword);
        }
        if (ec->kill >= 0)
                bind_if_not_bound(0, ec->kill, XFUNC_del_line);
        if (ec->werase >= 0)
                bind_if_not_bound(0, ec->werase, XFUNC_del_bword);
        if (ec->intr >= 0)
                bind_if_not_bound(0, ec->intr, XFUNC_abort);
        if (ec->quit >= 0)
                bind_if_not_bound(0, ec->quit, XFUNC_noop);
}

static int
x_set_mark(c)
        int c;
{
        xmp = xcp;
        return KSTD;
}

static int
x_kill_region(c)
        int c;
{
        int     rsize;
        char    *xr;

        if (xmp == NULL) {
                x_e_putc(BEL);
                return KSTD;
        }
        if (xmp > xcp) {
                rsize = xmp - xcp;
                xr = xcp;
        } else {
                rsize = xcp - xmp;
                xr = xmp;
        }
        x_goto(xr);
        x_delete(rsize, true);
        xmp = xr;
        return KSTD;
}

static int
x_xchg_point_mark(c)
        int c;
{
        char    *tmp;

        if (xmp == NULL) {
                x_e_putc(BEL);
                return KSTD;
        }
        tmp = xmp;
        xmp = xcp;
        x_goto( tmp );
        return KSTD;
}

static int
x_version(c)
        int c;
{
        char *o_xbuf = xbuf, *o_xend = xend;
        char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
        int lim = x_lastcp() - xbp;

        xbuf = xbp = xcp = ksh_version + 4;
        xend = xep = ksh_version + 4 + strlen(ksh_version + 4);
        x_redraw(lim);
        x_flush();

        c = x_e_getc();
        xbuf = o_xbuf;
        xend = o_xend;
        xbp = o_xbp;
        xep = o_xep;
        xcp = o_xcp;
        x_redraw(strlen(ksh_version));

        if (c < 0)
                return KSTD;
        /* This is what at&t ksh seems to do...  Very bizarre */
        if (c != ' ')
                x_e_ungetc(c);

        return KSTD;
}

static int
x_noop(c)
        int c;
{
        return KSTD;
}

#ifdef SILLY
static int
x_game_of_life(c)
        int c;
{
        char    newbuf [256+1];
        char    *ip, *op;
        int     i, len;

        i = xep - xbuf;
        *xep = 0;
        len = x_size_str(xbuf);
        xcp = xbp = xbuf;
        memmove(newbuf+1, xbuf, i);
        newbuf[0] = 'A';
        newbuf[i] = 'A';
        for (ip = newbuf+1, op = xbuf; --i >= 0; ip++, op++)  {
                /*  Empty space  */
                if (*ip < '@' || *ip == '_' || *ip == 0x7F)  {
                        /*  Two adults, make whoopee */
                        if (ip[-1] < '_' && ip[1] < '_')  {
                                /*  Make kid look like parents.  */
                                *op = '`' + ((ip[-1] + ip[1])/2)%32;
                                if (*op == 0x7F) /* Birth defect */
                                        *op = '`';
                        }
                        else
                                *op = ' ';      /* nothing happens */
                        continue;
                }
                /*  Child */
                if (*ip > '`')  {
                        /*  All alone, dies  */
                        if (ip[-1] == ' ' && ip[1] == ' ')
                                *op = ' ';
                        else    /*  Gets older */
                                *op = *ip-'`'+'@';
                        continue;
                }
                /*  Adult  */
                /*  Overcrowded, dies */
                if (ip[-1] >= '@' && ip[1] >= '@')  {
                        *op = ' ';
                        continue;
                }
                *op = *ip;
        }
        *op = 0;
        x_redraw(len);
        return KSTD;
}
#endif

/*
 *      File/command name completion routines
 */


static int
x_comp_comm(c)
        int c;
{
        do_complete(XCF_COMMAND, CT_COMPLETE);
        return KSTD;
}
static int
x_list_comm(c)
        int c;
{
        do_complete(XCF_COMMAND, CT_LIST);
        return KSTD;
}
static int
x_complete(c)
        int c;
{
        do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
        return KSTD;
}
static int
x_enumerate(c)
        int c;
{
        do_complete(XCF_COMMAND_FILE, CT_LIST);
        return KSTD;
}
static int
x_comp_file(c)
        int c;
{
        do_complete(XCF_FILE, CT_COMPLETE);
        return KSTD;
}
static int
x_list_file(c)
        int c;
{
        do_complete(XCF_FILE, CT_LIST);
        return KSTD;
}
static int
x_comp_list(c)
        int c;
{
        do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
        return KSTD;
}
static int
x_expand(c)
        int c;
{
        char **words;
        int nwords = 0;
        int start, end;
        int is_command;
        int i;

        nwords = x_cf_glob(XCF_FILE,
                xbuf, xep - xbuf, xcp - xbuf,
                &start, &end, &words, &is_command);

        if (nwords == 0) {
                x_e_putc(BEL);
                return KSTD;
        }

        x_goto(xbuf + start);
        x_delete(end - start, false);
        for (i = 0; i < nwords;) {
                if (x_escape(words[i], strlen(words[i]), x_emacs_putbuf) < 0 ||
                    (++i < nwords && x_ins(space) < 0))
                {
                        x_e_putc(BEL);
                        return KSTD;
                }
        }
        x_adjust();

        return KSTD;
}

/* type == 0 for list, 1 for complete and 2 for complete-list */
static void
do_complete(flags, type)
        int flags;      /* XCF_{COMMAND,FILE,COMMAND_FILE} */
        Comp_type type;
{
        char **words;
        int nwords;
        int start, end, nlen, olen;
        int is_command;
        int completed = 0;

        nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
                            &start, &end, &words, &is_command);
        /* no match */
        if (nwords == 0) {
                x_e_putc(BEL);
                return;
        }

        if (type == CT_LIST) {
                x_print_expansions(nwords, words, is_command);
                x_redraw(0);
                x_free_words(nwords, words);
                return;
        }

        olen = end - start;
        nlen = x_longest_prefix(nwords, words);
        /* complete */
        if (nwords == 1 || nlen > olen) {
                x_goto(xbuf + start);
                x_delete(olen, false);
                x_escape(words[0], nlen, x_emacs_putbuf);
                x_adjust();
                completed = 1;
        }
        /* add space if single non-dir match */
        if ((nwords == 1) && (!ISDIRSEP(words[0][nlen - 1]))) {
                x_ins(space);
                completed = 1;
        }

        if (type == CT_COMPLIST && !completed) {
                x_print_expansions(nwords, words, is_command);
                completed = 1;
        }

        if (completed)  
                x_redraw(0);    

        x_free_words(nwords, words);
}

/* NAME:
 *      x_adjust - redraw the line adjusting starting point etc.
 *
 * DESCRIPTION:
 *      This function is called when we have exceeded the bounds
 *      of the edit window.  It increments x_adj_done so that
 *      functions like x_ins and x_delete know that we have been
 *      called and can skip the x_bs() stuff which has already
 *      been done by x_redraw.
 *
 * RETURN VALUE:
 *      None
 */

static void
x_adjust()
{
  x_adj_done++;                 /* flag the fact that we were called. */
  /*
   * we had a problem if the prompt length > xx_cols / 2
   */
  if ((xbp = xcp - (x_displen / 2)) < xbuf)
    xbp = xbuf;
  xlp_valid = false;
  x_redraw(xx_cols);
  x_flush();
}

static int unget_char = -1;

static void
x_e_ungetc(c)
        int c;
{
        unget_char = c;
}

static int
x_e_getc()
{
        int c;
        
        if (unget_char >= 0) {
                c = unget_char;
                unget_char = -1;
        } else {
                if (macroptr)  {
                        c = (unsigned char) *macroptr++;
                        if (!*macroptr)
                                macroptr = (char *) 0;
                } else
                        c = x_getc();
        }

        return c <= CHARMASK ? c : (c & CHARMASK);
}

static void
x_e_putc(c)
        int c;
{
  if (c == '\r' || c == '\n')
    x_col = 0;
  if (x_col < xx_cols)
  {
    x_putc(c);
    switch(c)
    {
    case BEL:
      break;
    case '\r':
    case '\n':
    break;
    case '\b':
      x_col--;
      break;
    default:
      x_col++;
      break;
    }
  }
  if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
  {
    x_adjust();
  }
}

#ifdef DEBUG
static int
x_debug_info(c)
        int c;
{
        x_flush();
        shellf("\nksh debug:\n");
        shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n",
                 x_col, xx_cols, x_displen);
        shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep);
        shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf);
        shellf("\txlp == 0x%lx\n", (long) xlp);
        shellf("\txlp == 0x%lx\n", (long) x_lastcp());
        shellf(newline);
        x_redraw(-1);
        return 0;
}
#endif

static void
x_e_puts(s)
        const char *s;
{
  int   adj = x_adj_done;

  while (*s && adj == x_adj_done)
    x_e_putc(*s++);
}

/* NAME:
 *      x_set_arg - set an arg value for next function
 *
 * DESCRIPTION:
 *      This is a simple implementation of M-[0-9].
 *
 * RETURN VALUE:
 *      KSTD
 */

static int
x_set_arg(c)
        int c;
{
        int n = 0;
        int first = 1;

        c &= CHARMASK;  /* strip command prefix */
        for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0)
                n = n * 10 + (c - '0');
        if (c < 0 || first) {
                x_e_putc(BEL);
                x_arg = 1;
                x_arg_defaulted = 1;
        } else {
                x_e_ungetc(c);
                x_arg = n;
                x_arg_defaulted = 0;
        }
        return KSTD;
}


/* Comment or uncomment the current line. */
static int
x_comment(c)
        int c;
{
        int oldsize = x_size_str(xbuf);
        int len = xep - xbuf;
        int ret = x_do_comment(xbuf, xend - xbuf, &len);

        if (ret < 0)
                x_e_putc(BEL);
        else {
                xep = xbuf + len;
                *xep = '\0';
                xcp = xbp = xbuf;
                x_redraw(oldsize);
                if (ret > 0)
                        return x_newline('\n');
        }
        return KSTD;
}


/* NAME:
 *      x_prev_histword - recover word from prev command
 *
 * DESCRIPTION:
 *      This function recovers the last word from the previous
 *      command and inserts it into the current edit line.  If a
 *      numeric arg is supplied then the n'th word from the
 *      start of the previous command is used.
 *
 *      Bound to M-.
 *
 * RETURN VALUE:
 *      KSTD
 */

static int
x_prev_histword(c)
        int c;
{
  char *rcp;
  char *cp;

  cp = *histptr;
  if (!cp)
    x_e_putc(BEL);
  else if (x_arg_defaulted) {
    rcp = &cp[strlen(cp) - 1];
    /*
     * ignore white-space after the last word
     */
    while (rcp > cp && is_cfs(*rcp))
      rcp--;
    while (rcp > cp && !is_cfs(*rcp))
      rcp--;
    if (is_cfs(*rcp))
      rcp++;
    x_ins(rcp);
  } else {
    int i;

    rcp = cp;
    /*
     * ignore white-space at start of line
     */
    while (*rcp && is_cfs(*rcp))
      rcp++;
    while (x_arg-- > 1)
    {
      while (*rcp && !is_cfs(*rcp))
        rcp++;
      while (*rcp && is_cfs(*rcp))
        rcp++;
    }
    cp = rcp;
    while (*rcp && !is_cfs(*rcp))
      rcp++;
    i = *rcp;
    *rcp = '\0';
    x_ins(cp);
    *rcp = i;
  }
  return KSTD;
}

/* Uppercase N(1) words */
static int
x_fold_upper(c)
  int c;
{
        return x_fold_case('U');
}

/* Lowercase N(1) words */
static int
x_fold_lower(c)
  int c;
{
        return x_fold_case('L');
}

/* Lowercase N(1) words */
static int
x_fold_capitalize(c)
  int c;
{
        return x_fold_case('C');
}

/* NAME:
 *      x_fold_case - convert word to UPPER/lower/Capital case
 *
 * DESCRIPTION:
 *      This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
 *      to UPPER case, lower case or Capitalize words.
 *
 * RETURN VALUE:
 *      None
 */

static int
x_fold_case(c)
        int c;
{
        char *cp = xcp;
        
        if (cp == xep) {
                x_e_putc(BEL);
                return KSTD;
        }
        while (x_arg--) {
                /*
                 * first skip over any white-space
                 */
                while (cp != xep && is_mfs(*cp))
                        cp++;
                /*
                 * do the first char on its own since it may be
                 * a different action than for the rest.
                 */
                if (cp != xep) {
                        if (c == 'L') {         /* lowercase */
                                if (isupper((unsigned char)*cp))
                                        *cp = tolower((unsigned char)*cp);
                        } else {                /* uppercase, capitialize */
                                if (islower((unsigned char)*cp))
                                        *cp = toupper((unsigned char)*cp);
                        }
                        cp++;
                }
                /*
                 * now for the rest of the word
                 */
                while (cp != xep && !is_mfs((unsigned char)*cp)) {
                        if (c == 'U') {         /* uppercase */
                                if (islower((unsigned char)*cp))
                                        *cp = toupper((unsigned char)*cp);
                        } else {                /* lowercase, capitialize */
                                if (isupper((unsigned char)*cp))
                                        *cp = tolower((unsigned char)*cp);
                        }
                        cp++;
                }
        }
        x_goto(cp);
        return KSTD;
}

/* NAME:
 *      x_lastcp - last visible char
 *
 * SYNOPSIS:
 *      x_lastcp()
 *
 * DESCRIPTION:
 *      This function returns a pointer to that  char in the
 *      edit buffer that will be the last displayed on the
 *      screen.  The sequence:
 *
 *      for (cp = x_lastcp(); cp > xcp; cp)
 *        x_bs(*--cp);
 *
 *      Will position the cursor correctly on the screen.
 *
 * RETURN VALUE:
 *      cp or NULL
 */

static char *
x_lastcp()
{
  char *rcp;
  int i;

  if (!xlp_valid)
  {
    for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
      i += x_size(*rcp);
    xlp = rcp;
  }
  xlp_valid = true;
  return (xlp);
}

#endif /* EDIT */