#include "config.h"
#include <sys/ioctl.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "el.h"
#include "fcns.h"
#include "read.h"
#define EL_MAXMACRO 10
struct macros {
wchar_t **macro;
int level;
int offset;
};
struct el_read_t {
struct macros macros;
el_rfunc_t read_char;
int read_errno;
};
static int read_char(EditLine *, wchar_t *);
static int read_getcmd(EditLine *, el_action_t *, wchar_t *);
static void read_clearmacros(struct macros *);
static void read_pop(struct macros *);
protected int
read_init(EditLine *el)
{
struct macros *ma;
if ((el->el_read = malloc(sizeof(*el->el_read))) == NULL)
return -1;
ma = &el->el_read->macros;
if ((ma->macro = reallocarray(NULL, EL_MAXMACRO,
sizeof(*ma->macro))) == NULL) {
free(el->el_read);
return -1;
}
ma->level = -1;
ma->offset = 0;
el->el_read->read_char = read_char;
return 0;
}
protected void
read_end(struct el_read_t *el_read)
{
read_clearmacros(&el_read->macros);
free(el_read->macros.macro);
el_read->macros.macro = NULL;
}
protected int
el_read_setfn(struct el_read_t *el_read, el_rfunc_t rc)
{
el_read->read_char = (rc == EL_BUILTIN_GETCFN) ? read_char : rc;
return 0;
}
protected el_rfunc_t
el_read_getfn(struct el_read_t *el_read)
{
return el_read->read_char == read_char ?
EL_BUILTIN_GETCFN : el_read->read_char;
}
void
el_wpush(EditLine *el, const wchar_t *str)
{
struct macros *ma = &el->el_read->macros;
if (str != NULL && ma->level + 1 < EL_MAXMACRO) {
ma->level++;
if ((ma->macro[ma->level] = wcsdup(str)) != NULL)
return;
ma->level--;
}
terminal_beep(el);
terminal__flush(el);
}
static int
read_getcmd(EditLine *el, el_action_t *cmdnum, wchar_t *ch)
{
static const wchar_t meta = (wchar_t)0x80;
el_action_t cmd;
int num;
do {
if ((num = el_wgetc(el, ch)) != 1)
return -1;
#ifdef KANJI
if ((*ch & meta)) {
el->el_state.metanext = 0;
cmd = CcViMap[' '];
break;
} else
#endif
if (el->el_state.metanext) {
el->el_state.metanext = 0;
*ch |= meta;
}
if (*ch >= N_KEYS)
cmd = ED_INSERT;
else
cmd = el->el_map.current[(unsigned char) *ch];
if (cmd == ED_SEQUENCE_LEAD_IN) {
keymacro_value_t val;
switch (keymacro_get(el, ch, &val)) {
case XK_CMD:
cmd = val.cmd;
break;
case XK_STR:
el_wpush(el, val.str);
break;
case XK_NOD:
return -1;
default:
EL_ABORT((el->el_errfile, "Bad XK_ type \n"));
break;
}
}
} while (cmd == ED_SEQUENCE_LEAD_IN);
*cmdnum = cmd;
return 0;
}
static int
read_char(EditLine *el, wchar_t *cp)
{
char cbuf[MB_LEN_MAX];
int cbp = 0;
again:
el->el_signal->sig_no = 0;
switch (read(el->el_infd, cbuf + cbp, 1)) {
case -1:
if (errno == EINTR) {
switch (el->el_signal->sig_no) {
case SIGCONT:
el_set(el, EL_REFRESH);
case SIGWINCH:
sig_set(el);
goto again;
default:
break;
}
}
*cp = L'\0';
return -1;
case 0:
*cp = L'\0';
return 0;
default:
break;
}
for (;;) {
mbstate_t mbs;
++cbp;
memset(&mbs, 0, sizeof(mbs));
switch (mbrtowc(cp, cbuf, cbp, &mbs)) {
case (size_t)-1:
if (cbp > 1) {
cbuf[0] = cbuf[cbp - 1];
cbp = 0;
break;
} else {
cbp = 0;
goto again;
}
case (size_t)-2:
if ((el->el_flags & CHARSET_IS_UTF8) == 0 ||
cbp >= MB_LEN_MAX) {
errno = EILSEQ;
*cp = L'\0';
return -1;
}
goto again;
default:
return 1;
}
}
}
static void
read_pop(struct macros *ma)
{
int i;
free(ma->macro[0]);
for (i = 0; i < ma->level; i++)
ma->macro[i] = ma->macro[i + 1];
ma->level--;
ma->offset = 0;
}
static void
read_clearmacros(struct macros *ma)
{
while (ma->level >= 0)
free(ma->macro[ma->level--]);
ma->offset = 0;
}
int
el_wgetc(EditLine *el, wchar_t *cp)
{
struct macros *ma = &el->el_read->macros;
int num_read;
terminal__flush(el);
for (;;) {
if (ma->level < 0)
break;
if (ma->macro[0][ma->offset] == '\0') {
read_pop(ma);
continue;
}
*cp = ma->macro[0][ma->offset++];
if (ma->macro[0][ma->offset] == '\0') {
read_pop(ma);
}
return 1;
}
if (tty_rawmode(el) < 0)
return 0;
num_read = (*el->el_read->read_char)(el, cp);
if (num_read < 0)
el->el_read->read_errno = errno;
return num_read;
}
protected void
read_prepare(EditLine *el)
{
if (el->el_flags & HANDLE_SIGNALS)
sig_set(el);
if (el->el_flags & NO_TTY)
return;
if ((el->el_flags & (UNBUFFERED|EDIT_DISABLED)) == UNBUFFERED)
tty_rawmode(el);
el_resize(el);
re_clear_display(el);
ch_reset(el);
re_refresh(el);
if (el->el_flags & UNBUFFERED)
terminal__flush(el);
}
protected void
read_finish(EditLine *el)
{
if ((el->el_flags & UNBUFFERED) == 0)
(void) tty_cookedmode(el);
if (el->el_flags & HANDLE_SIGNALS)
sig_clr(el);
}
const wchar_t *
el_wgets(EditLine *el, int *nread)
{
int retval;
el_action_t cmdnum = 0;
int num;
wchar_t wc;
wchar_t ch, *cp;
int crlf = 0;
int nrb;
if (nread == NULL)
nread = &nrb;
*nread = 0;
el->el_read->read_errno = 0;
if (el->el_flags & NO_TTY) {
size_t idx;
cp = el->el_line.buffer;
while ((num = (*el->el_read->read_char)(el, &wc)) == 1) {
*cp = wc;
if (cp + 1 >= el->el_line.limit) {
idx = (cp - el->el_line.buffer);
if (!ch_enlargebufs(el, 2))
break;
cp = &el->el_line.buffer[idx];
}
cp++;
if (el->el_flags & UNBUFFERED)
break;
if (cp[-1] == '\r' || cp[-1] == '\n')
break;
}
if (num == -1 && errno == EINTR)
cp = el->el_line.buffer;
goto noedit;
}
#ifdef FIONREAD
if (el->el_tty.t_mode == EX_IO && el->el_read->macros.level < 0) {
int chrs = 0;
(void) ioctl(el->el_infd, FIONREAD, &chrs);
if (chrs == 0) {
if (tty_rawmode(el) < 0) {
errno = 0;
*nread = 0;
return NULL;
}
}
}
#endif
if ((el->el_flags & UNBUFFERED) == 0)
read_prepare(el);
if (el->el_flags & EDIT_DISABLED) {
size_t idx;
if ((el->el_flags & UNBUFFERED) == 0)
cp = el->el_line.buffer;
else
cp = el->el_line.lastchar;
terminal__flush(el);
while ((num = (*el->el_read->read_char)(el, &wc)) == 1) {
*cp = wc;
if (cp + 1 >= el->el_line.limit) {
idx = (cp - el->el_line.buffer);
if (!ch_enlargebufs(el, 2))
break;
cp = &el->el_line.buffer[idx];
}
cp++;
crlf = cp[-1] == '\r' || cp[-1] == '\n';
if (el->el_flags & UNBUFFERED)
break;
if (crlf)
break;
}
if (num == -1 && errno == EINTR)
cp = el->el_line.buffer;
goto noedit;
}
for (num = -1; num == -1;) {
if (read_getcmd(el, &cmdnum, &ch) == -1)
break;
if ((int)cmdnum >= el->el_map.nfunc)
continue;
el->el_state.thiscmd = cmdnum;
el->el_state.thisch = ch;
if (el->el_map.type == MAP_VI &&
el->el_map.current == el->el_map.key &&
el->el_chared.c_redo.pos < el->el_chared.c_redo.lim) {
if (cmdnum == VI_DELETE_PREV_CHAR &&
el->el_chared.c_redo.pos != el->el_chared.c_redo.buf
&& iswprint(el->el_chared.c_redo.pos[-1]))
el->el_chared.c_redo.pos--;
else
*el->el_chared.c_redo.pos++ = ch;
}
retval = (*el->el_map.func[cmdnum]) (el, ch);
el->el_state.lastcmd = cmdnum;
switch (retval) {
case CC_CURSOR:
re_refresh_cursor(el);
break;
case CC_REDISPLAY:
re_clear_lines(el);
re_clear_display(el);
case CC_REFRESH:
re_refresh(el);
break;
case CC_REFRESH_BEEP:
re_refresh(el);
terminal_beep(el);
break;
case CC_NORM:
break;
case CC_ARGHACK:
continue;
case CC_EOF:
if ((el->el_flags & UNBUFFERED) == 0)
num = 0;
else if (num == -1) {
*el->el_line.lastchar++ = CONTROL('d');
el->el_line.cursor = el->el_line.lastchar;
num = 1;
}
break;
case CC_NEWLINE:
num = (int)(el->el_line.lastchar - el->el_line.buffer);
break;
case CC_FATAL:
re_clear_display(el);
ch_reset(el);
read_clearmacros(&el->el_read->macros);
re_refresh(el);
break;
case CC_ERROR:
default:
terminal_beep(el);
terminal__flush(el);
break;
}
el->el_state.argument = 1;
el->el_state.doingarg = 0;
el->el_chared.c_vcmd.action = NOP;
if (el->el_flags & UNBUFFERED)
break;
}
terminal__flush(el);
if ((el->el_flags & UNBUFFERED) == 0) {
read_finish(el);
*nread = num != -1 ? num : 0;
} else {
*nread = (int)(el->el_line.lastchar - el->el_line.buffer);
}
goto done;
noedit:
el->el_line.cursor = el->el_line.lastchar = cp;
*cp = '\0';
*nread = (int)(el->el_line.cursor - el->el_line.buffer);
done:
if (*nread == 0) {
if (num == -1) {
*nread = -1;
if (el->el_read->read_errno)
errno = el->el_read->read_errno;
}
return NULL;
} else
return el->el_line.buffer;
}