#include "config.h"
#ifdef EMACS
#include <sys/queue.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef SMALL
# include <term.h>
# include <curses.h>
#endif
#include "sh.h"
#include "edit.h"
static Area aedit;
#define AEDIT &aedit
#define KSTD 0
#define KEOL 1
#define KINTR 2
typedef int (*kb_func)(int);
struct x_ftab {
kb_func xf_func;
const char *xf_name;
short xf_flags;
};
#define XF_ARG 1
#define XF_NOBIND 2
#define XF_PREFIX 4
#define is_cfs(c) (c == ' ' || c == '\t' || c == '"' || c == '\'')
#define is_mfs(c) (!(isalnum((unsigned char)c) || \
c == '_' || c == '$' || c & 0x80))
typedef enum {
CT_LIST,
CT_COMPLETE,
CT_COMPLIST
} Comp_type;
struct kb_entry {
TAILQ_ENTRY(kb_entry) entry;
unsigned char *seq;
int len;
struct x_ftab *ftab;
void *args;
};
TAILQ_HEAD(kb_list, kb_entry);
struct kb_list kblist = TAILQ_HEAD_INITIALIZER(kblist);
static char *xbuf;
static char *xend;
static char *xcp;
static char *xep;
static char *xbp;
static char *xlp;
static int x_adj_ok;
static int x_adj_done;
static int xx_cols;
static int x_col;
static int x_displen;
static int x_arg;
static int x_arg_defaulted;
static int xlp_valid;
static int x_tty;
static int x_bind_quiet;
static int (*x_last_command)(int);
static char **x_histp;
static int x_nextcmd;
static char *xmp;
#define KILLSIZE 20
static char *killstack[KILLSIZE];
static int killsp, killtp;
static int x_literal_set;
static int x_arg_set;
static char *macro_args;
static int prompt_skip;
static int prompt_redraw;
static int x_ins(char *);
static void x_delete(int, int);
static int x_bword(void);
static int x_fword(void);
static void x_goto(char *);
static void x_bs(int);
static int x_size_str(char *);
static int x_size(int);
static void x_zots(char *);
static void x_zotc(int);
static void x_load_hist(char **);
static int x_search(char *, int, int);
static int x_match(char *, char *);
static void x_redraw(int);
static void x_push(int);
static void x_adjust(void);
static void x_e_ungetc(int);
static int x_e_getc(void);
static int x_e_getu8(char *, int);
static void x_e_putc(int);
static void x_e_puts(const char *);
static int x_comment(int);
static int x_fold_case(int);
static char *x_lastcp(void);
static void do_complete(int, Comp_type);
static int isu8cont(unsigned char);
static int x_abort(int);
static int x_beg_hist(int);
static int x_clear_screen(int);
static int x_comp_comm(int);
static int x_comp_file(int);
static int x_complete(int);
static int x_del_back(int);
static int x_del_bword(int);
static int x_del_char(int);
static int x_del_fword(int);
static int x_del_line(int);
static int x_draw_line(int);
static int x_end_hist(int);
static int x_end_of_text(int);
static int x_enumerate(int);
static int x_eot_del(int);
static int x_error(int);
static int x_goto_hist(int);
static int x_ins_string(int);
static int x_insert(int);
static int x_kill(int);
static int x_kill_region(int);
static int x_list_comm(int);
static int x_list_file(int);
static int x_literal(int);
static int x_meta_yank(int);
static int x_mv_back(int);
static int x_mv_begin(int);
static int x_mv_bword(int);
static int x_mv_end(int);
static int x_mv_forw(int);
static int x_mv_fword(int);
static int x_newline(int);
static int x_next_com(int);
static int x_nl_next_com(int);
static int x_noop(int);
static int x_prev_com(int);
static int x_prev_histword(int);
static int x_search_char_forw(int);
static int x_search_char_back(int);
static int x_search_hist(int);
static int x_set_mark(int);
static int x_transpose(int);
static int x_xchg_point_mark(int);
static int x_yank(int);
static int x_comp_list(int);
static int x_expand(int);
static int x_fold_capitalize(int);
static int x_fold_lower(int);
static int x_fold_upper(int);
static int x_set_arg(int);
static int x_comment(int);
#ifdef DEBUG
static int x_debug_info(int);
#endif
static const struct x_ftab x_ftab[] = {
{ x_abort, "abort", 0 },
{ x_beg_hist, "beginning-of-history", 0 },
{ x_clear_screen, "clear-screen", 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_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_transpose, "transpose-chars", 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 },
{ 0, 0, 0 },
#ifdef DEBUG
{ x_debug_info, "debug-info", 0 },
#else
{ 0, 0, 0 },
#endif
{ 0, 0, 0 },
};
int
isu8cont(unsigned char c)
{
return (c & (0x80 | 0x40)) == 0x80;
}
int
x_emacs(char *buf, size_t len)
{
struct kb_entry *k, *kmatch = NULL;
char line[LINE + 1];
int at = 0, ntries = 0, submatch, ret;
const char *p;
xbp = xbuf = buf; xend = buf + len;
xlp = xcp = xep = buf;
*xcp = 0;
xlp_valid = true;
xmp = NULL;
x_histp = histptr + 1;
xx_cols = x_cols;
x_col = promptlen(prompt, &p);
prompt_skip = p - prompt;
x_adj_ok = 1;
prompt_redraw = 1;
if (x_col > xx_cols)
x_col = x_col - (x_col / xx_cols) * xx_cols;
x_displen = xx_cols - 2 - x_col;
x_adj_done = 0;
pprompt(prompt, 0);
if (x_displen < 1) {
x_col = 0;
x_displen = xx_cols - 2;
x_e_putc('\n');
prompt_redraw = 0;
}
if (x_nextcmd >= 0) {
int off = source->line - x_nextcmd;
if (histptr - history >= off)
x_load_hist(histptr - off);
x_nextcmd = -1;
}
x_literal_set = 0;
x_arg = -1;
x_last_command = NULL;
while (1) {
x_flush();
if ((at = x_e_getu8(line, at)) < 0)
return 0;
ntries++;
if (x_arg == -1) {
x_arg = 1;
x_arg_defaulted = 1;
}
if (x_literal_set) {
x_literal_set = 0;
submatch = 0;
} else {
submatch = 0;
kmatch = NULL;
TAILQ_FOREACH(k, &kblist, entry) {
if (at > k->len)
continue;
if (memcmp(k->seq, line, at) == 0) {
submatch++;
if (k->len == at)
kmatch = k;
}
if (submatch > 1)
break;
}
}
if (submatch == 1 && kmatch) {
if (kmatch->ftab->xf_func == x_ins_string &&
kmatch->args && !macro_args) {
macro_args = kmatch->args;
ret = KSTD;
} else
ret = kmatch->ftab->xf_func(line[at - 1]);
} else {
if (submatch)
continue;
if (ntries > 1) {
ret = x_error(0);
} else if (at > 1) {
x_ins(line);
ret = KSTD;
} else {
ret = x_insert(line[0]);
}
}
switch (ret) {
case KSTD:
if (kmatch)
x_last_command = kmatch->ftab->xf_func;
else
x_last_command = NULL;
break;
case KEOL:
ret = xep - xbuf;
return (ret);
break;
case KINTR:
trapsig(SIGINT);
x_mode(false);
unwind(LSHELL);
x_arg = -1;
break;
default:
bi_errorf("invalid return code");
}
at = ntries = 0;
if (x_arg_set)
x_arg_set = 0;
else
x_arg = -1;
}
}
static int
x_insert(int c)
{
char str[2];
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(int c)
{
return x_insert(c);
}
static int
x_do_ins(const char *cp, size_t 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(char *s)
{
char *cp = xcp;
int adj = x_adj_done;
if (x_do_ins(s, strlen(s)) < 0)
return -1;
xlp_valid = false;
x_lastcp();
x_adj_ok = (xcp >= xlp);
x_zots(cp);
if (adj == x_adj_done) {
for (cp = xlp; cp > xcp; )
x_bs(*--cp);
}
x_adj_ok = 1;
return 0;
}
static int
x_del_back(int c)
{
int col = xcp - xbuf;
if (col == 0) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > col)
x_arg = col;
while (x_arg < col && isu8cont(xcp[-x_arg]))
x_arg++;
x_goto(xcp - x_arg);
x_delete(x_arg, false);
return KSTD;
}
static int
x_del_char(int c)
{
int nleft = xep - xcp;
if (!nleft) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > nleft)
x_arg = nleft;
while (x_arg < nleft && isu8cont(xcp[x_arg]))
x_arg++;
x_delete(x_arg, false);
return KSTD;
}
static void
x_delete(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;
}
if (push)
x_push(nc);
xep -= nc;
cp = xcp;
j = 0;
i = nc;
while (i--) {
j += x_size((unsigned char)*cp++);
}
memmove(xcp, xcp+nc, xep - xcp + 1);
x_adj_ok = 0;
xlp_valid = false;
x_zots(xcp);
if ((i = xx_cols - 2 - x_col) > 0) {
j = (j < i) ? j : i;
i = j;
while (i--)
x_e_putc(' ');
i = j;
while (i--)
x_e_putc('\b');
}
x_adj_ok = 1;
xlp_valid = false;
for (cp = x_lastcp(); cp > xcp; )
x_bs(*--cp);
return;
}
static int
x_del_bword(int c)
{
x_delete(x_bword(), true);
return KSTD;
}
static int
x_mv_bword(int c)
{
(void)x_bword();
return KSTD;
}
static int
x_mv_fword(int c)
{
x_goto(xcp + x_fword());
return KSTD;
}
static int
x_del_fword(int c)
{
x_delete(x_fword(), true);
return KSTD;
}
static int
x_bword(void)
{
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(void)
{
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(char *cp)
{
if (cp < xbp || cp >= (xbp + x_displen)) {
xcp = cp;
x_adjust();
} else if (cp < xcp) {
while (cp < xcp)
x_bs((unsigned char)*--xcp);
} else if (cp > xcp) {
while (cp > xcp)
x_zotc((unsigned char)*xcp++);
}
}
static void
x_bs(int c)
{
int i;
i = x_size(c);
while (i--)
x_e_putc('\b');
}
static int
x_size_str(char *cp)
{
int size = 0;
while (*cp)
size += x_size(*cp++);
return size;
}
static int
x_size(int c)
{
if (c=='\t')
return 4;
if (iscntrl(c))
return 2;
if (isu8cont(c))
return 0;
return 1;
}
static void
x_zots(char *str)
{
int adj = x_adj_done;
if (str > xbuf && isu8cont(*str)) {
while (str > xbuf && isu8cont(*str))
str--;
x_e_putc('\b');
}
x_lastcp();
while (*str && str < xlp && adj == x_adj_done)
x_zotc(*str++);
}
static void
x_zotc(int c)
{
if (c == '\t') {
x_e_puts(" ");
} else if (iscntrl(c)) {
x_e_putc('^');
x_e_putc(UNCTRL(c));
} else
x_e_putc(c);
}
static int
x_mv_back(int c)
{
int col = xcp - xbuf;
if (col == 0) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > col)
x_arg = col;
while (x_arg < col && isu8cont(xcp[-x_arg]))
x_arg++;
x_goto(xcp - x_arg);
return KSTD;
}
static int
x_mv_forw(int c)
{
int nleft = xep - xcp;
if (!nleft) {
x_e_putc(BEL);
return KSTD;
}
if (x_arg > nleft)
x_arg = nleft;
while (x_arg < nleft && isu8cont(xcp[x_arg]))
x_arg++;
x_goto(xcp + x_arg);
return KSTD;
}
static int
x_search_char_forw(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(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(int c)
{
x_e_putc('\r');
x_e_putc('\n');
x_flush();
*xep++ = '\n';
return KEOL;
}
static int
x_end_of_text(int c)
{
x_zotc(edchars.eof);
x_putc('\r');
x_putc('\n');
x_flush();
return KEOL;
}
static int x_beg_hist(int c) { x_load_hist(history); return KSTD;}
static int x_end_hist(int c) { x_load_hist(histptr); return KSTD;}
static int x_prev_com(int c) { x_load_hist(x_histp - x_arg); return KSTD;}
static int x_next_com(int c) { x_load_hist(x_histp + x_arg); return KSTD;}
static int
x_goto_hist(int c)
{
if (x_arg_defaulted)
x_load_hist(history);
else
x_load_hist(histptr + x_arg - source->line);
return KSTD;
}
static void
x_load_hist(char **hp)
{
int oldsize;
if (hp < history || 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_redraw(oldsize);
x_goto(xep);
}
static int
x_nl_next_com(int c)
{
x_nextcmd = source->line - (histptr - x_histp) + 1;
return (x_newline(c));
}
static int
x_eot_del(int c)
{
if (xep == xbuf && x_arg_defaulted)
return (x_end_of_text(c));
else
return (x_del_char(c));
}
static kb_func
kb_find_hist_func(char c)
{
struct kb_entry *k;
char line[LINE + 1];
line[0] = c;
line[1] = '\0';
TAILQ_FOREACH(k, &kblist, entry)
if (!strcmp(k->seq, line))
return (k->ftab->xf_func);
return (x_insert);
}
static int
x_search_hist(int c)
{
int offset = -1;
char pat [256+1];
char *p = pat;
int (*f)(int);
*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 = kb_find_hist_func(c);
if (c == CTRL('[') || c == CTRL('@')) {
x_e_ungetc(c);
break;
} else if (f == x_search_hist)
offset = x_search(pat, 0, offset);
else if (f == x_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 == x_insert) {
if (p >= &pat[sizeof(pat) - 1]) {
x_e_putc(BEL);
continue;
}
*p++ = c, *p = '\0';
if (offset >= 0) {
offset = x_match(xbuf, pat);
if (offset >= 0) {
x_goto(xbuf + offset + (p - pat) -
(*pat == '^'));
continue;
}
}
offset = x_search(pat, 0, offset);
} else {
x_e_ungetc(c);
break;
}
}
if (offset < 0)
x_redraw(-1);
return KSTD;
}
static int
x_search(char *pat, int sameline, int offset)
{
char **hp;
int i;
for (hp = x_histp - (sameline ? 0 : 1) ; hp >= history; --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;
}
static int
x_match(char *str, char *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(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(int c)
{
x_goto(xep);
return KSTD;
}
static int
x_mv_begin(int c)
{
x_goto(xbuf);
return KSTD;
}
static int
x_draw_line(int c)
{
x_redraw(-1);
return KSTD;
}
static int
x_clear_screen(int c)
{
x_redraw(-2);
return KSTD;
}
static void
x_redraw(int limit)
{
int i, j, truncate = 0;
char *cp;
x_adj_ok = 0;
if (limit == -2) {
int cleared = 0;
#ifndef SMALL
if (cur_term == NULL && Flag(FTALKING))
initcurses();
if (cur_term != NULL && clear_screen != NULL) {
if (tputs(clear_screen, 1, x_putc) != ERR)
cleared = 1;
}
#endif
if (!cleared)
x_e_putc('\n');
}
else if (limit == -1)
x_e_putc('\n');
else if (limit >= 0)
x_e_putc('\r');
x_flush();
if (xbp == xbuf) {
x_col = promptlen(prompt, NULL);
if (x_col > xx_cols)
truncate = (x_col / xx_cols) * xx_cols;
if (prompt_redraw)
pprompt(prompt + prompt_skip, truncate);
}
if (x_col > xx_cols)
x_col = x_col - (x_col / xx_cols) * xx_cols;
x_displen = xx_cols - 2 - x_col;
if (x_displen < 1) {
x_col = 0;
x_displen = xx_cols - 2;
}
xlp_valid = false;
x_lastcp();
x_zots(xbp);
if (xbp != xbuf || xep > xlp)
limit = xx_cols;
if (limit >= 0) {
if (xep > xlp)
i = 0;
else
i = limit - (xlp - xbp);
for (j = 0; j < i && x_col < (xx_cols - 2); j++)
x_e_putc(' ');
i = ' ';
if (xep > xlp) {
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;
#ifdef DEBUG
x_flush();
#endif
return;
}
static int
x_transpose(int c)
{
char tmp;
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;
}
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 {
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(int c)
{
x_literal_set = 1;
return KSTD;
}
static int
x_kill(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;
while (x_arg < lastcol && isu8cont(xbuf[x_arg]))
x_arg++;
ndel = x_arg - col;
if (ndel < 0) {
x_goto(xbuf + x_arg);
ndel = -ndel;
}
x_delete(ndel, true);
return KSTD;
}
static void
x_push(int nchars)
{
char *cp = str_nsave(xcp, nchars, AEDIT);
afree(killstack[killsp], AEDIT);
killstack[killsp] = cp;
killsp = (killsp + 1) % KILLSIZE;
}
static int
x_yank(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(int c)
{
int len;
if ((x_last_command != x_yank && x_last_command != x_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(int c)
{
xlp = xep = xcp = xbp = xbuf;
xlp_valid = true;
*xcp = 0;
return KINTR;
}
static int
x_error(int c)
{
x_e_putc(BEL);
return KSTD;
}
static char *
kb_encode(const char *s)
{
static char l[LINE + 1];
int at = 0;
l[at] = '\0';
while (*s) {
if (*s == '^') {
s++;
if (*s >= '?')
l[at++] = CTRL(*s);
else {
l[at++] = '^';
s--;
}
} else
l[at++] = *s;
l[at] = '\0';
s++;
}
return (l);
}
static char *
kb_decode(const char *s)
{
static char l[LINE + 1];
unsigned int i, at = 0;
l[0] = '\0';
for (i = 0; i < strlen(s); i++) {
if (iscntrl((unsigned char)s[i])) {
l[at++] = '^';
l[at++] = UNCTRL(s[i]);
} else
l[at++] = s[i];
l[at] = '\0';
}
return (l);
}
static int
kb_match(char *s)
{
int len = strlen(s);
struct kb_entry *k;
TAILQ_FOREACH(k, &kblist, entry) {
if (len > k->len)
continue;
if (memcmp(k->seq, s, len) == 0)
return (1);
}
return (0);
}
static void
kb_del(struct kb_entry *k)
{
TAILQ_REMOVE(&kblist, k, entry);
free(k->args);
afree(k, AEDIT);
}
static struct kb_entry *
kb_add_string(kb_func func, void *args, char *str)
{
unsigned int ele, count;
struct kb_entry *k;
struct x_ftab *xf = NULL;
for (ele = 0; ele < NELEM(x_ftab); ele++)
if (x_ftab[ele].xf_func == func) {
xf = (struct x_ftab *)&x_ftab[ele];
break;
}
if (xf == NULL)
return (NULL);
if (kb_match(str)) {
if (x_bind_quiet == 0)
bi_errorf("duplicate binding for %s", kb_decode(str));
return (NULL);
}
count = strlen(str);
k = alloc(sizeof *k + count + 1, AEDIT);
k->seq = (unsigned char *)(k + 1);
k->len = count;
k->ftab = xf;
k->args = args ? strdup(args) : NULL;
strlcpy(k->seq, str, count + 1);
TAILQ_INSERT_TAIL(&kblist, k, entry);
return (k);
}
static struct kb_entry *
kb_add(kb_func func, ...)
{
va_list ap;
unsigned char ch;
unsigned int i;
char line[LINE + 1];
va_start(ap, func);
for (i = 0; i < sizeof(line) - 1; i++) {
ch = va_arg(ap, unsigned int);
if (ch == 0)
break;
line[i] = ch;
}
va_end(ap);
line[i] = '\0';
return (kb_add_string(func, NULL, line));
}
static void
kb_print(struct kb_entry *k)
{
if (!(k->ftab->xf_flags & XF_NOBIND))
shprintf("%s = %s\n",
kb_decode(k->seq), k->ftab->xf_name);
else if (k->args) {
shprintf("%s = ", kb_decode(k->seq));
shprintf("'%s'\n", kb_decode(k->args));
}
}
int
x_bind(const char *a1, const char *a2,
int macro,
int list)
{
unsigned int i;
struct kb_entry *k, *kb;
char in[LINE + 1];
if (x_tty == 0) {
bi_errorf("cannot bind, not a tty");
return (1);
}
if (list) {
for (i = 0; i < NELEM(x_ftab); i++) {
if (x_ftab[i].xf_name == NULL)
continue;
if (x_ftab[i].xf_name &&
!(x_ftab[i].xf_flags & XF_NOBIND))
shprintf("%s\n", x_ftab[i].xf_name);
}
return (0);
}
if (a1 == NULL) {
TAILQ_FOREACH(k, &kblist, entry)
kb_print(k);
return (0);
}
snprintf(in, sizeof in, "%s", kb_encode(a1));
if (a2 == NULL) {
TAILQ_FOREACH(k, &kblist, entry)
if (!strcmp(k->seq, in)) {
kb_print(k);
return (0);
}
shprintf("%s = %s\n", kb_decode(a1), "auto-insert");
return (0);
}
if (strlen(a2) == 0) {
TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
if (!strcmp(k->seq, in)) {
kb_del(k);
break;
}
return (0);
}
if (macro) {
TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
if (!strcmp(k->seq, in)) {
kb_del(k);
break;
}
kb_add_string(x_ins_string, kb_encode(a2), in);
return (0);
}
for (i = 0; i < NELEM(x_ftab); i++) {
if (x_ftab[i].xf_name == NULL)
continue;
if (!strcmp(x_ftab[i].xf_name, a2)) {
TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
if (!strcmp(k->seq, in)) {
kb_del(k);
break;
}
kb_add_string(x_ftab[i].xf_func, NULL, in);
return (0);
}
}
bi_errorf("%s: no such function", a2);
return (1);
}
void
x_init_emacs(void)
{
x_tty = 1;
ainit(AEDIT);
x_nextcmd = -1;
TAILQ_INIT(&kblist);
kb_add(x_abort, CTRL('G'), 0);
kb_add(x_mv_back, CTRL('B'), 0);
kb_add(x_mv_back, CTRL('X'), CTRL('D'), 0);
kb_add(x_mv_bword, CTRL('['), 'b', 0);
kb_add(x_beg_hist, CTRL('['), '<', 0);
kb_add(x_mv_begin, CTRL('A'), 0);
kb_add(x_fold_capitalize, CTRL('['), 'C', 0);
kb_add(x_fold_capitalize, CTRL('['), 'c', 0);
kb_add(x_comment, CTRL('['), '#', 0);
kb_add(x_complete, CTRL('['), CTRL('['), 0);
kb_add(x_comp_comm, CTRL('X'), CTRL('['), 0);
kb_add(x_comp_file, CTRL('['), CTRL('X'), 0);
kb_add(x_comp_list, CTRL('I'), 0);
kb_add(x_comp_list, CTRL('['), '=', 0);
kb_add(x_del_back, CTRL('?'), 0);
kb_add(x_del_back, CTRL('H'), 0);
kb_add(x_del_char, CTRL('['), '[', '3', '~', 0);
kb_add(x_del_bword, CTRL('W'), 0);
kb_add(x_del_bword, CTRL('['), CTRL('?'), 0);
kb_add(x_del_bword, CTRL('['), CTRL('H'), 0);
kb_add(x_del_bword, CTRL('['), 'h', 0);
kb_add(x_del_fword, CTRL('['), 'd', 0);
kb_add(x_next_com, CTRL('N'), 0);
kb_add(x_next_com, CTRL('X'), 'B', 0);
kb_add(x_fold_lower, CTRL('['), 'L', 0);
kb_add(x_fold_lower, CTRL('['), 'l', 0);
kb_add(x_end_hist, CTRL('['), '>', 0);
kb_add(x_mv_end, CTRL('E'), 0);
kb_add(x_end_of_text, CTRL('_'), 0);
kb_add(x_eot_del, CTRL('D'), 0);
kb_add(x_xchg_point_mark, CTRL('X'), CTRL('X'), 0);
kb_add(x_expand, CTRL('['), '*', 0);
kb_add(x_mv_forw, CTRL('F'), 0);
kb_add(x_mv_forw, CTRL('X'), 'C', 0);
kb_add(x_mv_fword, CTRL('['), 'f', 0);
kb_add(x_goto_hist, CTRL('['), 'g', 0);
kb_add(x_kill, CTRL('K'), 0);
kb_add(x_enumerate, CTRL('['), '?', 0);
kb_add(x_list_comm, CTRL('X'), '?', 0);
kb_add(x_list_file, CTRL('X'), CTRL('Y'), 0);
kb_add(x_newline, CTRL('J'), 0);
kb_add(x_newline, CTRL('M'), 0);
kb_add(x_nl_next_com, CTRL('O'), 0);
kb_add(x_prev_histword, CTRL('['), '.', 0);
kb_add(x_prev_histword, CTRL('['), '_', 0);
kb_add(x_literal, CTRL('^'), 0);
kb_add(x_clear_screen, CTRL('L'), 0);
kb_add(x_search_char_back, CTRL('['), CTRL(']'), 0);
kb_add(x_search_char_forw, CTRL(']'), 0);
kb_add(x_search_hist, CTRL('R'), 0);
kb_add(x_set_mark, CTRL('['), ' ', 0);
kb_add(x_transpose, CTRL('T'), 0);
kb_add(x_prev_com, CTRL('P'), 0);
kb_add(x_prev_com, CTRL('X'), 'A', 0);
kb_add(x_fold_upper, CTRL('['), 'U', 0);
kb_add(x_fold_upper, CTRL('['), 'u', 0);
kb_add(x_literal, CTRL('V'), 0);
kb_add(x_yank, CTRL('Y'), 0);
kb_add(x_meta_yank, CTRL('['), 'y', 0);
kb_add(x_prev_com, CTRL('['), '[', 'A', 0);
kb_add(x_next_com, CTRL('['), '[', 'B', 0);
kb_add(x_mv_forw, CTRL('['), '[', 'C', 0);
kb_add(x_mv_back, CTRL('['), '[', 'D', 0);
kb_add(x_prev_com, CTRL('['), 'O', 'A', 0);
kb_add(x_next_com, CTRL('['), 'O', 'B', 0);
kb_add(x_mv_forw, CTRL('['), 'O', 'C', 0);
kb_add(x_mv_back, CTRL('['), 'O', 'D', 0);
kb_add(x_mv_begin, CTRL('['), '[', 'H', 0);
kb_add(x_mv_end, CTRL('['), '[', 'F', 0);
kb_add(x_mv_begin, CTRL('['), 'O', 'H', 0);
kb_add(x_mv_end, CTRL('['), 'O', 'F', 0);
kb_add(x_mv_begin, CTRL('['), '[', '1', '~', 0);
kb_add(x_mv_end, CTRL('['), '[', '4', '~', 0);
kb_add(x_mv_begin, CTRL('['), '[', '7', '~', 0);
kb_add(x_mv_end, CTRL('['), '[', '8', '~', 0);
kb_add(x_set_arg, CTRL('['), '0', 0);
kb_add(x_set_arg, CTRL('['), '1', 0);
kb_add(x_set_arg, CTRL('['), '2', 0);
kb_add(x_set_arg, CTRL('['), '3', 0);
kb_add(x_set_arg, CTRL('['), '4', 0);
kb_add(x_set_arg, CTRL('['), '5', 0);
kb_add(x_set_arg, CTRL('['), '6', 0);
kb_add(x_set_arg, CTRL('['), '7', 0);
kb_add(x_set_arg, CTRL('['), '8', 0);
kb_add(x_set_arg, CTRL('['), '9', 0);
kb_add(x_mv_end, CTRL('['), '[', '1', ';', '5', 'A', 0);
kb_add(x_mv_begin, CTRL('['), '[', '1', ';', '5', 'B', 0);
kb_add(x_mv_fword, CTRL('['), '[', '1', ';', '5', 'C', 0);
kb_add(x_mv_bword, CTRL('['), '[', '1', ';', '5', 'D', 0);
}
void
x_emacs_keys(X_chars *ec)
{
x_bind_quiet = 1;
if (ec->erase >= 0) {
kb_add(x_del_back, ec->erase, 0);
kb_add(x_del_bword, CTRL('['), ec->erase, 0);
}
if (ec->kill >= 0)
kb_add(x_del_line, ec->kill, 0);
if (ec->werase >= 0)
kb_add(x_del_bword, ec->werase, 0);
if (ec->intr >= 0)
kb_add(x_abort, ec->intr, 0);
if (ec->quit >= 0)
kb_add(x_noop, ec->quit, 0);
x_bind_quiet = 0;
}
static int
x_set_mark(int c)
{
xmp = xcp;
return KSTD;
}
static int
x_kill_region(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(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_noop(int c)
{
return KSTD;
}
static int
x_comp_comm(int c)
{
do_complete(XCF_COMMAND, CT_COMPLETE);
return KSTD;
}
static int
x_list_comm(int c)
{
do_complete(XCF_COMMAND, CT_LIST);
return KSTD;
}
static int
x_complete(int c)
{
do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
return KSTD;
}
static int
x_enumerate(int c)
{
do_complete(XCF_COMMAND_FILE, CT_LIST);
return KSTD;
}
static int
x_comp_file(int c)
{
do_complete(XCF_FILE, CT_COMPLETE);
return KSTD;
}
static int
x_list_file(int c)
{
do_complete(XCF_FILE, CT_LIST);
return KSTD;
}
static int
x_comp_list(int c)
{
do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
return KSTD;
}
static int
x_expand(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_do_ins) < 0 ||
(++i < nwords && x_ins(" ") < 0)) {
x_e_putc(BEL);
return KSTD;
}
}
x_adjust();
return KSTD;
}
static void
do_complete(int flags,
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);
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);
if (nwords == 1 || nlen > olen) {
x_goto(xbuf + start);
x_delete(olen, false);
x_escape(words[0], nlen, x_do_ins);
x_adjust();
completed = 1;
}
if (nwords == 1 && words[0][nlen - 1] != '/') {
x_ins(" ");
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);
}
static void
x_adjust(void)
{
x_adj_done++;
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(int c)
{
unget_char = c;
}
static int
x_e_getc(void)
{
int c;
if (unget_char >= 0) {
c = unget_char;
unget_char = -1;
} else if (macro_args) {
c = *macro_args++;
if (!c) {
macro_args = NULL;
c = x_getc();
}
} else
c = x_getc();
return c;
}
static int
x_e_getu8(char *buf, int off)
{
int c, cc, len;
c = x_e_getc();
if (c == -1)
return -1;
buf[off++] = c;
if ((c & 0xf8) == 0xf0 && c < 0xf5)
len = 4;
else if ((c & 0xf0) == 0xe0)
len = 3;
else if ((c & 0xe0) == 0xc0 && c > 0xc1)
len = 2;
else
len = 1;
for (; len > 1; len--) {
cc = x_e_getc();
if (cc == -1)
break;
if (isu8cont(cc) == 0 ||
(c == 0xe0 && len == 3 && cc < 0xa0) ||
(c == 0xed && len == 3 && cc > 0x9f) ||
(c == 0xf0 && len == 4 && cc < 0x90) ||
(c == 0xf4 && len == 4 && cc > 0x8f)) {
x_e_ungetc(cc);
break;
}
buf[off++] = cc;
}
buf[off] = '\0';
return off;
}
static void
x_e_putc(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:
if (!isu8cont(c))
x_col++;
break;
}
}
if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
x_adjust();
}
#ifdef DEBUG
static int
x_debug_info(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("\n");
x_redraw(-1);
return 0;
}
#endif
static void
x_e_puts(const char *s)
{
int adj = x_adj_done;
while (*s && adj == x_adj_done)
x_e_putc(*s++);
}
static int
x_set_arg(int c)
{
int n = 0;
int first = 1;
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;
x_arg_set = 1;
}
return KSTD;
}
static int
x_comment(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;
}
static int
x_prev_histword(int c)
{
char *rcp;
char *cp;
cp = *histptr;
if (!cp)
x_e_putc(BEL);
else if (x_arg_defaulted) {
rcp = &cp[strlen(cp) - 1];
while (rcp > cp && is_cfs(*rcp))
rcp--;
while (rcp > cp && !is_cfs(*rcp))
rcp--;
if (is_cfs(*rcp))
rcp++;
x_ins(rcp);
} else {
rcp = cp;
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++;
c = *rcp;
*rcp = '\0';
x_ins(cp);
*rcp = c;
}
return KSTD;
}
static int
x_fold_upper(int c)
{
return x_fold_case('U');
}
static int
x_fold_lower(int c)
{
return x_fold_case('L');
}
static int
x_fold_capitalize(int c)
{
return x_fold_case('C');
}
static int
x_fold_case(int c)
{
char *cp = xcp;
if (cp == xep) {
x_e_putc(BEL);
return KSTD;
}
while (x_arg--) {
while (cp != xep && is_mfs(*cp))
cp++;
if (cp != xep) {
if (c == 'L') {
if (isupper((unsigned char)*cp))
*cp = tolower((unsigned char)*cp);
} else {
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
}
cp++;
}
while (cp != xep && !is_mfs(*cp)) {
if (c == 'U') {
if (islower((unsigned char)*cp))
*cp = toupper((unsigned char)*cp);
} else {
if (isupper((unsigned char)*cp))
*cp = tolower((unsigned char)*cp);
}
cp++;
}
}
x_goto(cp);
return KSTD;
}
static char *
x_lastcp(void)
{
char *rcp;
int i;
if (!xlp_valid) {
for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
i += x_size((unsigned char)*rcp);
xlp = rcp;
}
xlp_valid = true;
return (xlp);
}
#endif