/***********************************************************************
 *                                                                      *
 *               This software is part of the ast package               *
 *          Copyright (c) 1982-2014 AT&T Intellectual Property          *
 *                      and is licensed under the                       *
 *                 Eclipse Public License, Version 1.0                  *
 *                    by AT&T Intellectual Property                     *
 *                                                                      *
 *                A copy of the License is available at                 *
 *          http://www.eclipse.org/org/documents/epl-v10.html           *
 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
 *                                                                      *
 *              Information and Software Systems Research               *
 *                            AT&T Research                             *
 *                           Florham Park NJ                            *
 *                                                                      *
 *                    David Korn <dgkorn@gmail.com>                     *
 *                                                                      *
 ***********************************************************************/
// Original version by Michael T. Veach
// Adapted for ksh by David Korn
//
// One line screen editor for any program
//

// The following is provided by:
//
//   Matthijs N. Melchior
//   AT&T Network Systems International
//   APT Nederland
//   HV BZ335 x2962
//   hvlpb!mmelchio
//
// These are now on by default
//
//  ESH_NFIRST
//      -  A ^N as first history related command after the prompt will move to the next command
//      relative to the last known history position. It will not start at the position where the
//      last command was entered as is done by the ^P command.  Every history related command will
//      set both the current and last position.  Executing a command will only set the current
//      position.
//
//  ESH_KAPPEND
//      -  Successive kill and delete commands will accumulate their data in the kill buffer, by
//      appending or prepending as appropriate. This mode will be reset by any command not adding
//      something to the kill buffer.
//
//  ESH_BETTER
//      -  Some enhancements:
//              - argument for a macro is passed to its replacement
//              - ^X^H command to find out about history position (debugging)
//              - ^X^D command to show any debugging info
//
// I do not pretend these for changes are completely independent, but you can use them to separate
// features.
//
#include "config_ast.h"  // IWYU pragma: keep

#include <ctype.h>
#include <setjmp.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wchar.h>

#include "ast.h"
#include "defs.h"
#include "edit.h"
#include "history.h"
#include "stk.h"
#include "terminal.h"

#undef putchar
#define putchar(ed, c) ed_putchar(ed, c)
#define beep() ed_ringbell()

#define gencpy(a, b) ed_gencpy(a, b)
#define genncpy(a, b, n) ed_genncpy(a, b, n)
#define genlen(str) ed_genlen(str)
static int print(int);
static int _isword(int);
#define isword(c) _isword(out[c])
#define digit(c) ((c & ~STRIP) == 0 && isdigit(c))

typedef struct _emacs_ {
    wchar_t *screen;  // pointer to window buffer
    wchar_t *cursor;  // Cursor in real screen
    int mark;
    int in_mult;
    char cr_ok;
    char CntrlO;
    char overflow;  // screen overflow flag set
    char scvalid;   // screen is up to date
    char lastdraw;  // last update type
    int offset;     // screen offset
    enum {
        CRT = 0,  // crt terminal
        PAPER     // paper terminal
    } terminal;
    Histloc_t _location;
    int prevdirection;
    Edit_t *ed;  // pointer to edit data
} Emacs_t;

#define editb (*ep->ed)
#define eol editb.e_eol
#define cur editb.e_cur
#define hline editb.e_hline
#define hloff editb.e_hloff
#define hismin editb.e_hismin
#define usrkill editb.e_kill
#define usrlnext editb.e_lnext
#define usreof editb.e_eof
#define usrerase editb.e_erase
#define crallowed editb.e_crlf
#define Prompt editb.e_prompt
#define plen editb.e_plen
#define kstack editb.e_killbuf
#define lstring editb.e_search
#define lookahead editb.e_lookahead
#define env editb.e_env
#define raw editb.e_raw
#define histlines editb.e_hismax
#define w_size editb.e_wsize
#define drawbuff editb.e_inbuf
#define killing editb.e_mode
#define location ep->_location

#define LBUF 100
#define KILLCHAR UKILL
#define ERASECHAR UERASE
#define EOFCHAR UEOF
#define LNEXTCHAR ULNEXT
#define DELETE 0177

//
// A large lookahead helps when the user is inserting characters in the middle of the line.
//
typedef enum {
    FIRST,    // first time thru for logical line, prompt on screen
    REFRESH,  // redraw entire screen
    APPEND,   // append char before cursor to screen
    UPDATE,   // update the screen as need be
    FINAL     // update screen even if pending look ahead
} Draw_t;

static void draw(Emacs_t *, Draw_t);
static int escape(Emacs_t *, wchar_t *, int);
static void putstring(Emacs_t *, char *);
static void search(Emacs_t *, wchar_t *, int);
static void setcursor(Emacs_t *, int, int);
static void show_info(Emacs_t *, const char *);
static void xcommands(Emacs_t *, int);

int ed_emacsread(void *context, int fd, char *buff, int scend, int reedit) {
    Edit_t *ed = context;
    int c;
    int i;
    wchar_t *out;
    int count;
    Emacs_t *ep = ed->e_emacs;
    int adjust, oadjust;
    char backslash;
    wchar_t *kptr;
    char prompt[PRSIZE];
    wchar_t Screen[MAXLINE];

    memset(Screen, 0, sizeof(Screen));
    if (!ep) {
        ep = ed->e_emacs = calloc(1, sizeof(Emacs_t));
        ep->ed = ed;
        ep->prevdirection = 1;
        location.hist_command = -5;
    }
    Prompt = prompt;
    ep->screen = Screen;
    ep->lastdraw = FINAL;
    if (tty_raw(STDERR_FILENO, 0) < 0) {
        return reedit ? reedit : ed_read(context, fd, buff, scend, 0);
    }
    raw = 1;

    // This mess in case the read system call fails.
    ed_setup(ep->ed, fd, reedit);
    out = (wchar_t *)roundof((ptrdiff_t)buff, sizeof(wchar_t));
    if (reedit) ed_internal(buff, out);
    if (!kstack) {
        kstack = malloc(CHARSIZE * MAXLINE);
        kstack[0] = '\0';
    }
    drawbuff = out;
    if (location.hist_command == -5) {  // to be initialized
        kstack[0] = '\0';               // also clear kstack...
        location.hist_command = hline;
        location.hist_line = hloff;
    }
    if (location.hist_command <= hismin) {  // don't start below minimum
        location.hist_command = hismin + 1;
        location.hist_line = 0;
    }
    ep->in_mult = hloff;  // save pos in last command
    i = sigsetjmp(env, 0);
    if (i != 0) {
        if (ep->ed->e_multiline) {
            cur = eol;
            draw(ep, FINAL);
            ed_flush(ep->ed);
        }
        tty_cooked(STDERR_FILENO);
        if (i == UEOF) return 0;  // EOF
        return -1;                // some other error
    }
    out[reedit] = 0;
    if (scend + plen > (MAXLINE - 2)) scend = (MAXLINE - 2) - plen;
    ep->mark = 0;
    cur = eol;
    draw(ep, reedit ? REFRESH : FIRST);
    adjust = -1;
    backslash = 0;
    if (ep->CntrlO) ed_ungetchar(ep->ed, cntl('N'));
    ep->CntrlO = 0;
    while ((c = ed_getchar(ep->ed, 0)) != (-1)) {
        if (backslash) {
            backslash = 0;
            if (c == usrerase || c == usrkill || (!print(c) && (c != '\r' && c != '\n'))) {
                // Accept a backslashed character.
                cur--;
                out[cur++] = c;
                out[eol] = '\0';
                draw(ep, APPEND);
                continue;
            }
        }
        if (c == usrkill) {
            c = KILLCHAR;
        } else if (c == usrerase) {
            c = ERASECHAR;
        } else if (c == usrlnext) {
            c = LNEXTCHAR;
        } else if ((c == usreof) && (eol == 0)) {
            c = EOFCHAR;
        }
        if (--killing <= 0) killing = 0;  // reset killing flag
        oadjust = count = adjust;
        if (count < 0) count = 1;
        adjust = -1;
        i = cur;
        if (c != '\t' && c != ESC && !digit(c)) ep->ed->e_tabcount = 0;
        switch (c) {
            case LNEXTCHAR: {
                c = ed_getchar(ep->ed, 2);
                goto do_default_processing;
            }
            case cntl('V'): {
                show_info(ep, fmtident(e_version));
                continue;
            }
            case '\0': {
                ep->mark = i;
                continue;
            }
            case cntl('X'): {
                xcommands(ep, count);
                continue;
            }
            case EOFCHAR: {
                ed_flush(ep->ed);
                tty_cooked(STDERR_FILENO);
                return 0;
            }
            case '\t': {
                if ((cur > 0 || ep->ed->e_tabcount) && ep->ed->sh->nextprompt) {
                    if (ep->ed->e_tabcount == 0) {
                        ep->ed->e_tabcount = 1;
                        ed_ungetchar(ep->ed, ESC);
                        adjust = escape(ep, out, oadjust);
                        continue;
                    } else if (ep->ed->e_tabcount == 1) {
                        ed_ungetchar(ep->ed, '=');
                        adjust = escape(ep, out, oadjust);
                        continue;
                    }
                    ep->ed->e_tabcount = 0;
                } else {
                    ep->ed->e_tabcount = 1;
                }
                goto do_default_processing;
            }
            case cntl('Y'): {
                c = genlen(kstack);
                if ((c + eol) > scend) {
                    beep();
                    continue;
                }
                ep->mark = i;
                for (i = eol; i >= cur; i--) out[c + i] = out[i];
                kptr = kstack;
                while ((i = *kptr++)) out[cur++] = i;
                draw(ep, UPDATE);
                eol = genlen(out);
                continue;
            }
            case '\n':
            case '\r': {
                c = '\n';
                goto process;
            }
            case DELETE:  // delete char, 0x7f
            case '\b':    // backspace, ^h
            case ERASECHAR: {
                if (count > i) count = i;
                kptr = &kstack[count];  // move old contents here
                if (killing)            // prepend to killbuf
                {
                    c = genlen(kstack) + CHARSIZE;  // include '\0'
                    while (c--) {                   // copy stuff
                        kptr[c] = kstack[c];
                    }
                } else {
                    *kptr = 0;  // this is end of data
                }
                killing = 2;  // we are killing
                i -= count;
                eol -= count;
                genncpy(kstack, out + i, cur - i);
                gencpy(out + i, out + cur);
                ep->mark = i;
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('W'): {
                ++killing;  // keep killing flag
                if (ep->mark > eol) ep->mark = eol;
                if (ep->mark == i) continue;
                if (ep->mark > i) {
                    adjust = ep->mark - i;
                    ed_ungetchar(ep->ed, cntl('D'));
                    continue;
                }
                adjust = i - ep->mark;
                ed_ungetchar(ep->ed, usrerase);
                continue;
            }
            case cntl('D'): {
                ep->mark = i;
                if (killing) {
                    kptr = &kstack[genlen(kstack)];  // append here
                } else {
                    kptr = kstack;
                }
                killing = 2;  // we are now killing
                while ((count--) && (eol > 0) && (i < eol)) {
                    *kptr++ = out[i];
                    eol--;
                    while (1) {
                        if ((out[i] = out[(i + 1)]) == 0) break;
                        i++;
                    }
                    i = cur;
                }
                *kptr = '\0';
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('C'):
            case cntl('F'): {
                int cntlC = (c == cntl('C'));
                while (count-- && eol > i) {
                    if (cntlC) {
                        c = out[i];
                        if ((c & ~STRIP) == 0 && islower(c)) {
                            c += 'A' - 'a';
                            out[i] = c;
                        }
                    }
                    i++;
                }
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl(']'): {
                c = ed_getchar(ep->ed, 1);
                if ((count == 0) || (count > eol)) {
                    beep();
                    continue;
                }
                if (out[i]) i++;
                while (i < eol) {
                    if (out[i] == c && --count == 0) break;
                    i++;
                }
                if (i < eol) {
                    cur = i;
                    draw(ep, UPDATE);
                    continue;
                }
                i = 0;
                while (i < cur) {
                    if (out[i] == c && --count == 0) break;
                    i++;
                };
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('B'): {
                if (count > i) count = i;
                i -= count;
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('T'): {
                if ((sh_isoption(ep->ed->sh, SH_EMACS)) && (eol != i)) i++;
                if (i >= 2) {
                    c = out[i - 1];
                    out[i - 1] = out[i - 2];
                    out[i - 2] = c;
                } else {
                    if (sh_isoption(ep->ed->sh, SH_EMACS)) i--;
                    beep();
                    continue;
                }
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('A'): {
                i = 0;
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('E'): {
                i = eol;
                cur = i;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('U'): {
                adjust = 4 * count;
                continue;
            }
            case KILLCHAR: {
                cur = 0;
                oadjust = -1;
            }
            // FALLTHRU
            case cntl('K'): {
                if (oadjust >= 0) {
                    killing = 2;  // set killing signal
                    ep->mark = count;
                    ed_ungetchar(ep->ed, cntl('W'));
                    continue;
                }
                i = cur;
                eol = i;
                ep->mark = i;
                if (killing) {  // append to kill buffer
                    gencpy(&kstack[genlen(kstack)], &out[i]);
                } else {
                    gencpy(kstack, &out[i]);
                }
                killing = 2;  // set killing signal
                out[i] = 0;
                draw(ep, UPDATE);
                if (c != KILLCHAR) continue;
                if (ep->terminal == PAPER) {
                    putchar(ep->ed, '\n');
                    putstring(ep, Prompt);
                }
                c = ed_getchar(ep->ed, 0);
                if (c != usrkill) {
                    ed_ungetchar(ep->ed, c);
                    continue;
                }
                if (ep->terminal == PAPER) {
                    ep->terminal = CRT;
                } else {
                    ep->terminal = PAPER;
                    putchar(ep->ed, '\n');
                    putstring(ep, Prompt);
                }
                continue;
            }
            case cntl('L'): {
                if (!ep->ed->e_nocrnl) ed_crlf(ep->ed);
                draw(ep, REFRESH);
                ep->ed->e_nocrnl = 0;
                continue;
            }
            case cntl('['): {
                adjust = escape(ep, out, oadjust);
                continue;
            }
            case cntl('R'): {
                search(ep, out, count);
                eol = genlen(out);
                cur = eol;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('P'): {
                if (ep->ed->hlist) {
                    if (ep->ed->hoff == 0) {
                        beep();
                        continue;
                    }
                    ep->ed->hoff--;
                    ed_histlist(ep->ed, *ep->ed->hlist != 0);
                    draw(ep, REFRESH);
                    continue;
                }
                if (count <= hloff) {
                    hloff -= count;
                } else {
                    hline -= count - hloff;
                    hloff = 0;
                }
                if (hline <= hismin) {
                    hline = hismin + 1;
                    beep();
                }
                // This is shared in common with [ctrl-N].
                location.hist_command = hline;  // save current position
                location.hist_line = hloff;
                cur = 0;
                draw(ep, UPDATE);
                hist_copy((char *)out, MAXLINE, hline, hloff);
                ed_internal((char *)(out), out);
                eol = genlen(out);
                cur = eol;
                draw(ep, UPDATE);
                continue;
            }
            case cntl('O'): {
                location.hist_command = hline;
                location.hist_line = hloff;
                ep->CntrlO = 1;
                c = '\n';
                goto process;
            }
            case cntl('N'): {
                if (ep->ed->hlist) {
                    if (ep->ed->hoff >= ep->ed->hmax) {
                        beep();
                        continue;
                    }
                    ep->ed->hoff++;
                    ed_histlist(ep->ed, *ep->ed->hlist != 0);
                    draw(ep, REFRESH);
                    continue;
                }
                hline = location.hist_command;  // start at saved position
                hloff = location.hist_line;
                location = hist_locate(shgd->hist_ptr, hline, hloff, count);
                if (location.hist_command > histlines) {
                    beep();
                    location.hist_command = histlines;
                    location.hist_line = ep->in_mult;
                }
                hline = location.hist_command;
                hloff = location.hist_line;
                // This is shared in common with [ctrl-P].
                location.hist_command = hline;  // save current position
                location.hist_line = hloff;
                cur = 0;
                draw(ep, UPDATE);
                hist_copy((char *)out, MAXLINE, hline, hloff);
                ed_internal((char *)(out), out);
                eol = genlen(out);
                cur = eol;
                draw(ep, UPDATE);
                continue;
            }
            default: {
            do_default_processing:
                if ((eol + 1) >= (scend)) /*  will not fit on line */
                {
                    ed_ungetchar(ep->ed, c); /* save character for next line */
                    goto process;
                }
                for (i = ++eol; i > cur; i--) out[i] = out[i - 1];
                backslash = (c == '\\');
                out[cur++] = c;
                draw(ep, APPEND);
                continue;
            }
        }
    }

process:
    if (c == (-1)) {
        lookahead = 0;
        beep();
        *out = '\0';
    }
    draw(ep, FINAL);
    tty_cooked(STDERR_FILENO);
    if (ed->e_nlist) {
        ed->e_nlist = 0;
        stkset(stkstd, ed->e_stkptr, ed->e_stkoff);
    }
    if (c == '\n') {
        out[eol++] = '\n';
        out[eol] = '\0';
        ed_crlf(ep->ed);
    }
    ed_external(out, buff);
    i = (int)strlen(buff);
    if (i) return i;
    return -1;
}

static void show_info(Emacs_t *ep, const char *str) {
    wchar_t *out = drawbuff;
    int c;
    wchar_t string[LBUF];
    int sav_cur = cur;

    // Save current line.
    genncpy(string, out, sizeof(string) / sizeof(*string));
    *out = 0;
    cur = 0;
    ed_internal(str, out);
    draw(ep, UPDATE);
    c = ed_getchar(ep->ed, 0);
    if (c != ' ') ed_ungetchar(ep->ed, c);
    // Restore line.
    cur = sav_cur;
    genncpy(out, string, sizeof(string) / sizeof(*string));
    draw(ep, UPDATE);
}

static void putstring(Emacs_t *ep, char *sp) {
    int c;

    while ((c = *sp++)) putchar(ep->ed, c);
}

static int escape(Emacs_t *ep, wchar_t *out, int count) {
    int i, value;
    int digit, ch;

    digit = 0;
    value = 0;
    while ((i = ed_getchar(ep->ed, 0)), digit(i)) {
        value *= 10;
        value += (i - '0');
        digit = 1;
    }
    if (digit) {
        ed_ungetchar(ep->ed, i);
        ++killing;  // don't modify killing signal
        return value;
    }
    value = count;
    if (value < 0) value = 1;
    switch (ch = i) {
        case cntl('V'): {
            show_info(ep, fmtident(e_version));
            return -1;
        }
        case ' ': {
            ep->mark = cur;
            return -1;
        }
        case '+': {  // M-+ = append next kill
            killing = 2;
            return -1;  // no argument for next command
        }
        case 'p': {  // M-p == ^W^Y (copy stack == kill & yank)
            ed_ungetchar(ep->ed, cntl('Y'));
            ed_ungetchar(ep->ed, cntl('W'));
            killing = 0;  // start fresh
            return -1;
        }
        case 'l':  // M-l == lower-case
        case 'd':
        case 'c':
        case 'f': {
            i = cur;
            while (value-- && i < eol) {
                while ((out[i]) && (!isword(i))) i++;
                while ((out[i]) && (isword(i))) i++;
            }
            if (ch == 'l') {
                value = i - cur;
                while (value-- > 0) {
                    i = out[cur];
                    if ((i & ~STRIP) == 0 && isupper(i)) {
                        i += 'a' - 'A';
                        out[cur] = i;
                    }
                    cur++;
                }
                draw(ep, UPDATE);
                return -1;
            }

            else if (ch == 'f') {
                cur = i;
                draw(ep, UPDATE);
                return -1;
            } else if (ch == 'c') {
                ed_ungetchar(ep->ed, cntl('C'));
                return i - cur;
            }
            if (i - cur) {
                ed_ungetchar(ep->ed, cntl('D'));
                ++killing;  // keep killing signal
                return i - cur;
            }
            beep();
            return -1;
        }
        case 'b':
        case DELETE:
        case '\b':
        case 'h': {
            i = cur;
            while (value-- && i > 0) {
                i--;
                while ((i > 0) && (!isword(i))) i--;
                while ((i > 0) && (isword(i - 1))) i--;
            }
            if (ch == 'b') {
                cur = i;
                draw(ep, UPDATE);
                return -1;
            }
            ed_ungetchar(ep->ed, usrerase);
            ++killing;
            return cur - i;
        }
        case '>': {
            ed_ungetchar(ep->ed, cntl('N'));
            if (ep->in_mult) {
                location.hist_command = histlines;
                location.hist_line = ep->in_mult - 1;
            } else {
                location.hist_command = histlines - 1;
                location.hist_line = 0;
            }
            return 0;
        }
        case '<': {
            ed_ungetchar(ep->ed, cntl('P'));
            hloff = 0;
            hline = hismin + 1;
            return 0;
        }
        case '#': {
            ed_ungetchar(ep->ed, '\n');
            ed_ungetchar(ep->ed, (out[0] == '#') ? cntl('D') : '#');
            ed_ungetchar(ep->ed, cntl('A'));
            return -1;
        }
        case '_':
        case '.': {
            wchar_t name[MAXLINE];
            char buf[MAXLINE];
            char *ptr;
            ptr = hist_word(buf, MAXLINE, (count ? count : -1));
            if (ptr == 0) {
                beep();
                break;
            }
            if ((eol - cur) >= sizeof(name)) {
                beep();
                return -1;
            }
            ep->mark = cur;
            gencpy(name, &out[cur]);
            while (*ptr) {
                out[cur++] = *ptr++;
                eol++;
            }
            gencpy(&out[cur], name);
            draw(ep, UPDATE);
            return -1;
        }
        case '\n':
        case '\t': {
            if (!ep->ed->hlist) {
                beep();
                break;
            }
            if (ch == '\n') ed_ungetchar(ep->ed, '\n');
        }
        // FALLTHRU
        case cntl('['): {  // filename completion
            if (ep->ed->hlist) {
                value += ep->ed->hoff;
                if (value > ep->ed->nhlist) {
                    beep();
                } else {
                    value = histlines - ep->ed->hlist[value - 1]->index;
                    ed_histlist(ep->ed, 0);
                    ed_ungetchar(ep->ed, cntl('P'));
                    return value;
                }
            }
            i = '\\';
        }
        // FALLTHRU
        case '*':    // filename expansion
        case '=': {  // escape = - list all matching file names
            ep->mark = cur;
            ch = i;
            if (i == '\\' && ep->mark > 0 && out[ep->mark - 1] == '/') i = '=';
            if (ed_expand(ep->ed, (char *)out, &cur, &eol, ch, count) < 0) {
                if (ep->ed->e_tabcount == 1) {
                    ep->ed->e_tabcount = 2;
                    ed_ungetchar(ep->ed, cntl('\t'));
                    return -1;
                }
                beep();
            } else if (i == '=' || (i == '\\' && out[cur - 1] == '/')) {
                draw(ep, REFRESH);
                if (count > 0 || i == '\\') {
                    ep->ed->e_tabcount = 0;
                } else {
                    i = ed_getchar(ep->ed, 0);
                    ed_ungetchar(ep->ed, i);
                    if (digit(i)) ed_ungetchar(ep->ed, ESC);
                }
            } else {
                if (i == '\\' && cur > ep->mark && (out[cur - 1] == '/' || out[cur - 1] == ' ')) {
                    ep->ed->e_tabcount = 0;
                }
                draw(ep, UPDATE);
            }
            return -1;
        }
        // Search back for character.
        case cntl(']'): {  // feature not in book
            int c = ed_getchar(ep->ed, 1);
            if ((value == 0) || (value > eol)) {
                beep();
                return -1;
            }
            i = cur;
            if (i > 0) i--;
            while (i >= 0) {
                if (out[i] == c && --value == 0) break;
                i--;
            }
            if (i >= 0) {
                cur = i;
                draw(ep, UPDATE);
                return -1;
            }
            i = eol;
            while (i > cur) {
                if (out[i] == c && --value == 0) break;
                i--;
            };
            cur = i;
            draw(ep, UPDATE);
            return -1;
        }
#ifdef _cmd_tput
        case cntl('L'): {  // clear screen
            sh_trap(ep->ed->sh, "tput clear", 0);
            draw(ep, REFRESH);
            return -1;
        }
#endif               // _cmd_tput
        case 'O':    // after running top <ESC>O instead of <ESC>[
        case '[': {  // feature not in book
            switch (i = ed_getchar(ep->ed, 1)) {
                case 'A': {
                    if (!ep->ed->hlist && cur > 0 && eol == cur &&
                        (cur < (SEARCHSIZE - 2) || ep->prevdirection == -2)) {
                        if (ep->lastdraw == APPEND && ep->prevdirection != -2) {
                            out[cur] = 0;
                            gencpy((wchar_t *)lstring + 1, out);
                            ed_external((wchar_t *)lstring + 1, lstring + 1);
                            *lstring = '^';
                            ep->prevdirection = -2;
                        }
                        if (*lstring) {
                            ed_ungetchar(ep->ed, '\r');
                            ed_ungetchar(ep->ed, cntl('R'));
                            return -1;
                        }
                    }
                    *lstring = 0;
                    ed_ungetchar(ep->ed, cntl('P'));
                    return -1;
                }
                case 'B': {
                    ed_ungetchar(ep->ed, cntl('N'));
                    return -1;
                }
                case 'C': {
                    ed_ungetchar(ep->ed, cntl('F'));
                    return -1;
                }
                case 'D': {
                    ed_ungetchar(ep->ed, cntl('B'));
                    return -1;
                }
                case 'H': {
                    ed_ungetchar(ep->ed, cntl('A'));
                    return -1;
                }
                case 'Y': {
                    ed_ungetchar(ep->ed, cntl('E'));
                    return -1;
                }
                default: { ed_ungetchar(ep->ed, i); }
            }
            i = '_';
        }
        // FALLTHRU
        default: {                                  // look for user defined macro definitions
            if (ed_macro(ep->ed, i)) return count;  // pass argument to macro
            beep();
            return -1;
        }
    }
    return -1;
}

//
// This routine process all commands starting with ^X.
//
static void xcommands(Emacs_t *ep, int count) {
    int i = ed_getchar(ep->ed, 0);
    UNUSED(count);

    switch (i) {
        case cntl('X'): {  // exchange dot and mark
            if (ep->mark > eol) ep->mark = eol;
            i = ep->mark;
            ep->mark = cur;
            cur = i;
            draw(ep, UPDATE);
            return;
        }
        case cntl('E'): {  // invoke emacs on current command
            if (ed_fulledit(ep->ed) == -1) {
                beep();
            } else {
                ed_internal((char *)drawbuff, drawbuff);
                ed_ungetchar(ep->ed, '\n');
            }
            return;
        }
        case cntl('H'): {  // ^X^H show history info
            char hbuf[MAXLINE];

            strlcpy(hbuf, "Current command ", sizeof(hbuf));
            strlcat(hbuf, fmtbase(hline, 0, 0), sizeof(hbuf));
            if (hloff) {
                strlcat(hbuf, " (line ", sizeof(hbuf));
                strlcat(hbuf, fmtbase(hloff + 1, 0, 0), sizeof(hbuf));
                strlcat(hbuf, ")", sizeof(hbuf));
            }
            if ((hline != location.hist_command) || (hloff != location.hist_line)) {
                strlcat(hbuf, "; Previous command ", sizeof(hbuf));
                strlcat(hbuf, fmtbase(location.hist_command, 0, 0), sizeof(hbuf));
                if (location.hist_line) {
                    strlcat(hbuf, " (line ", sizeof(hbuf));
                    strlcat(hbuf, fmtbase(location.hist_line + 1, 0, 0), sizeof(hbuf));
                    strlcat(hbuf, ")", sizeof(hbuf));
                }
            }
            show_info(ep, hbuf);
            return;
        }
#if 0   /* debugging, modify as required */
        case cntl('D'):  { // ^X^D show debugging info
                char debugbuf[MAXLINE];

                strcpy(debugbuf, "count=");
                strcat(debugbuf, fmtbase(count, 0, 0));
                strcat(debugbuf, " eol=");
                strcat(debugbuf, fmtbase(eol, 0, 0));
                strcat(debugbuf, " cur=");
                strcat(debugbuf, fmtbase(cur, 0, 0));
                strcat(debugbuf, " crallowed=");
                strcat(debugbuf, fmtbase(crallowed, 0, 0));
                strcat(debugbuf, " plen=");
                strcat(debugbuf, fmtbase(plen, 0, 0));
                strcat(debugbuf, " w_size=");
                strcat(debugbuf, fmtbase(w_size, 0, 0));

                show_info(ep,debugbuf);
                return;
        }
#endif  // debugging code
        default: {
            beep();
            return;
        }
    }
}

static void search(Emacs_t *ep, wchar_t *out, int direction) {
    int i, sl;
    wchar_t str_buff[LBUF];
    wchar_t *string = drawbuff;
    int sav_cur = cur;  // save current line

    genncpy(str_buff, string, sizeof(str_buff) / sizeof(*str_buff));
    string[0] = '^';
    string[1] = 'R';
    string[2] = '\0';
    sl = 2;
    cur = sl;
    draw(ep, UPDATE);
    while ((i = ed_getchar(ep->ed, 1)) && (i != '\r') && (i != '\n')) {
        if (i == usrerase || i == DELETE || i == '\b' || i == ERASECHAR) {
            if (sl > 2) {
                string[--sl] = '\0';
                cur = sl;
                draw(ep, UPDATE);
            } else {
                goto restore;
            }
            continue;
        }
        if (i == ep->ed->e_intr) goto restore;
        if (i == usrkill) {
            beep();
            goto restore;
        }
        if (i == '\\') {
            string[sl++] = '\\';
            string[sl] = '\0';
            cur = sl;
            draw(ep, APPEND);
            i = ed_getchar(ep->ed, 1);
            string[--sl] = '\0';
        }
        string[sl++] = i;
        string[sl] = '\0';
        cur = sl;
        draw(ep, APPEND);
    }
    i = genlen(string);

    if ((ep->prevdirection == -2 && i != 2) || direction != 1) ep->prevdirection = -1;
    if (direction < 1) {
        ep->prevdirection = -ep->prevdirection;
        direction = 1;
    } else {
        direction = -1;
    }
    if (i != 2) {
        ed_external(string, (char *)string);
        // Is it okay if the string is truncated?
        (void)strlcpy(lstring, ((char *)string) + 2, SEARCHSIZE);
        ep->prevdirection = direction;
    } else {
        direction = ep->prevdirection;
    }
    location = hist_find(shgd->hist_ptr, (char *)lstring, hline, 1, direction);
    i = location.hist_command;
    if (i > 0) {
        hline = i;
        hloff = location.hist_line = 0;  // display first line of multi line command
        hist_copy((char *)out, MAXLINE, hline, hloff);
        ed_internal((char *)out, out);
        return;
    }
    if (i < 0) {
        beep();
        location.hist_command = hline;
        location.hist_line = hloff;
    }
restore:
    genncpy(string, str_buff, sizeof(str_buff) / sizeof(*str_buff));
    cur = sav_cur;
    return;
}

//
// Adjust screen to agree with inputs: logical line and cursor. If 'first' assume screen is blank.
// Prompt is always kept on the screen.
//
static void draw(Emacs_t *ep, Draw_t option) {
#define NORMAL ' '
#define LOWER '<'
#define BOTH '*'
#define UPPER '>'

    wchar_t *sptr;                 // pointer within screen
    wchar_t nscreen[2 * MAXLINE];  // new entire screen
    wchar_t *ncursor;              // new cursor
    wchar_t *nptr;                 // pointer to New screen
    char longline;                 // line overflow
    wchar_t *logcursor;
    wchar_t *nscend;  // end of logical screen
    int i;

    nptr = nscreen;
    sptr = drawbuff;
    logcursor = sptr + cur;
    longline = NORMAL;
    ep->lastdraw = option;

    if (option == FIRST || option == REFRESH) {
        ep->overflow = NORMAL;
        ep->cursor = ep->screen;
        ep->offset = 0;
        ep->cr_ok = crallowed;
        if (option == FIRST) {
            ep->scvalid = 1;
            return;
        }
        *ep->cursor = '\0';
        putstring(ep, Prompt);  // start with prompt
    }

    // Do not update screen if pending characters.
    if ((lookahead) && (option != FINAL)) {
        ep->scvalid = 0;  // screen is out of date, APPEND will not work
        return;
    }

    // If in append mode, cursor at end of line, screen up to date, the previous character was a
    // 'normal' character, and the window has room for another character. Then output the character
    // and adjust the screen only.
    i = cur ? *(logcursor - 1) : 0;  // last character inserted
    if (option == FINAL) {
        if (ep->ed->hlist) ed_histlist(ep->ed, 0);
    } else if ((option == UPDATE || option == APPEND) && drawbuff[0] == '#' && cur > 1 &&
               cur == eol && drawbuff[cur - 1] != '*') {
        int n;
        drawbuff[cur + 1] = 0;
        ed_external(drawbuff, (char *)drawbuff);
        n = ed_histgen(ep->ed, (char *)drawbuff);
        ed_internal((char *)drawbuff, drawbuff);
        if (ep->ed->hlist) {
            ed_histlist(ep->ed, n);
            putstring(ep, Prompt);
            ed_setcursor(ep->ed, ep->screen, 0, ep->cursor - ep->screen, 0);
        } else {
            ed_ringbell();
        }
    }

    if ((option == APPEND) && (ep->scvalid) && (*logcursor == '\0') && print(i) &&
        ((ep->cursor - ep->screen) < (w_size - 1))) {
        putchar(ep->ed, i);
        *ep->cursor++ = i;
        *ep->cursor = '\0';
        return;
    }

    // Copy the line.
    ncursor = nptr + ed_virt_to_phys(ep->ed, sptr, nptr, cur, 0, 0);
    nptr += genlen(nptr);
    sptr += genlen(sptr);
    nscend = nptr - 1;
    if (sptr == logcursor) ncursor = nptr;

    // Does ncursor appear on the screen? If not, adjust the screen offset so it does.
    i = ncursor - nscreen;

    if ((ep->offset && i <= ep->offset) || (i >= (ep->offset + w_size))) {
        // Center the cursor on the screen.
        ep->offset = i - (w_size >> 1);
        if (--ep->offset < 0) ep->offset = 0;
    }

    // Is the range of screen[0] thru screen[w_size] up-to-date with nscreen[offset] thru
    // nscreen[offset+w_size] ? If not, update as need be.
    nptr = &nscreen[ep->offset];
    sptr = ep->screen;

    i = w_size;

    while (i-- > 0) {
        if (*nptr == '\0') {
            *(nptr + 1) = '\0';
            *nptr = ' ';
        }
        if (*sptr == '\0') {
            *(sptr + 1) = '\0';
            *sptr = ' ';
        }
        if (*nptr == *sptr) {
            nptr++;
            sptr++;
            continue;
        }
        setcursor(ep, sptr - ep->screen, *nptr);
        *sptr++ = *nptr++;
        while (*nptr == MARKER) {
            if (*sptr == '\0') *(sptr + 1) = '\0';
            *sptr++ = *nptr++;
            i--;
            ep->cursor++;
        }
    }
    if (ep->ed->e_multiline && option == REFRESH && ep->ed->e_nocrnl == 0) {
        ed_setcursor(ep->ed, ep->screen, ep->cursor - ep->screen, ep->ed->e_peol, -1);
    }

    // Screen overflow checks.
    if (nscend >= &nscreen[ep->offset + w_size]) {
        if (ep->offset > 0) {
            longline = BOTH;
        } else {
            longline = UPPER;
        }
    } else {
        if (ep->offset > 0) longline = LOWER;
    }

    // Update screen overflow indicator if need be.
    if (longline != ep->overflow) {
        setcursor(ep, w_size, longline);
        ep->overflow = longline;
    }
    i = (ncursor - nscreen) - ep->offset;
    setcursor(ep, i, 0);
    if (option == FINAL && ep->ed->e_multiline) setcursor(ep, nscend + 1 - nscreen, 0);
    ep->scvalid = 1;
    return;
}

//
// Put the cursor to the <newp> position within screen buffer. If <c> is non-zero then output this
// character. Cursor is set to reflect the change.
//
static void setcursor(Emacs_t *ep, int newp, int c) {
    int oldp = ep->cursor - ep->screen;

    newp = ed_setcursor(ep->ed, ep->screen, oldp, newp, 0);
    if (c) {
        putchar(ep->ed, c);
        newp++;
    }
    ep->cursor = ep->screen + newp;
    return;
}

static int print(int c) { return (c & ~STRIP) == 0 && isprint(c); }

static int _isword(int c) { return (c & ~STRIP) || isalnum(c) || c == '_'; }
