summaryrefslogtreecommitdiff
path: root/src/breedline/breedline.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/breedline/breedline.c')
-rw-r--r--src/breedline/breedline.c1490
1 files changed, 1490 insertions, 0 deletions
diff --git a/src/breedline/breedline.c b/src/breedline/breedline.c
new file mode 100644
index 0000000..3169e0d
--- /dev/null
+++ b/src/breedline/breedline.c
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+
+#include "breedline/breedline.h"
+#include "breedline/mm.h"
+
+#ifndef TRUE
+# define FALSE 0
+# define TRUE (!FALSE)
+#endif
+
+#define BRL_UNUSED(var) (void)var
+
+#ifdef __GNUC__
+# define BRL_UNLIKELY(cond) __builtin_expect((cond), 0)
+#else
+# define BRL_UNLIKELY(cond) __builtin_expect((cond), 0)
+#endif
+
+#define BRL_CURSOR_START "\x1b[0G" /* move cursor to start of line */
+#define BRL_CURSOR_FORW "\x1b[%dC" /* move cursor forward by %d */
+#define BRL_ERASE_RIGHT "\x1b[0K" /* erase right of cursor */
+
+
+/*
+ * breedline modes
+ */
+
+typedef enum {
+ BRL_MODE_NORMAL, /* normal editing mode */
+ BRL_MODE_SEARCH_FORW, /* searching forward */
+ BRL_MODE_SEARCH_BACK, /* searching backward */
+} brl_mode_t;
+
+
+/*
+ * breedline key types
+ */
+
+typedef enum {
+ BRL_TYPE_INVALID = 0x000, /* invalid key */
+ BRL_TYPE_SELF = 0x100, /* self-inserting (normal) key */
+ BRL_TYPE_COMMAND = 0x200, /* editing command key */
+ BRL_TYPE_CSEQ = 0x400, /* control sequence indicator */
+ BRL_TYPE_MASK = 0x700, /* key type mask */
+} brl_type_t;
+
+#define BRL_INPUT_TYPE(in) ((in) & BRL_TYPE_MASK)
+#define BRL_TAG_INPUT(type, c) ((type & BRL_TYPE_MASK) | ((c) & ~BRL_TYPE_MASK))
+#define BRL_INPUT_DATA(in) ((in) & ~BRL_TYPE_MASK)
+
+/*
+ * breedline commands
+ */
+
+typedef enum {
+ BRL_CMD_INVALID = 0x00, /* invalid input */
+ BRL_CMD_SELF = 0xff, /* self-inserting input */
+ BRL_CMD_FORWARD = 0x01, /* cursor forward */
+ BRL_CMD_BACKWARD, /* cursor backward */
+ BRL_CMD_PREV_LINE, /* previous line from history */
+ BRL_CMD_NEXT_LINE, /* next line from history */
+ BRL_CMD_ERASE_BEFORE, /* erase before cursor */
+ BRL_CMD_ERASE_AT, /* erase at cursor */
+ BRL_CMD_LINE_START, /* cursor to start of line */
+ BRL_CMD_LINE_END, /* cursor to end of line */
+ BRL_CMD_ERASE_REST, /* erase till the end of line */
+ BRL_CMD_ERASE_ALL, /* erase the whole line */
+ BRL_CMD_YANK, /* yank at insertion point */
+ BRL_CMD_PREV_WORD, /* cursor to previous word boundary */
+ BRL_CMD_NEXT_WORD, /* cursor to next word boundary */
+ BRL_CMD_CANCEL, /* cancel special command or mode */
+ BRL_CMD_ENTER, /* enter input */
+ BRL_CMD_REDRAW, /* redraw input prompt */
+ BRL_CMD_SEARCH_BACK, /* search history backward */
+ BRL_CMD_SEARCH_FORW, /* search history forward */
+} brl_cmd_t;
+
+
+/*
+ * breedline extended control sequences
+ */
+
+typedef struct {
+ const char *seq; /* control sequence */
+ int len; /* sequence length */
+ int key; /* mapped to this key */
+} extmap_t;
+
+
+/*
+ * key mapping
+ */
+
+#undef CTRL
+#define CTRL(code) ((code) & 0x1f)
+#define ESC 0x1b
+#define DEL 0x7f
+#define BELL 0x7
+
+#define MAP(in, out) [(in)] = (out)
+#define MAP_RANGE(min, max, out) [(min) ... (max)] = (out)
+#define CMD(cmd) BRL_TAG_INPUT(BRL_TYPE_COMMAND, BRL_CMD_##cmd)
+
+static int key_map[256] = {
+ MAP_RANGE(' ' , '~', BRL_TYPE_SELF ),
+ MAP(CTRL('b') , CMD(BACKWARD) ),
+ MAP(CTRL('f') , CMD(FORWARD) ),
+ MAP(CTRL('p') , CMD(PREV_LINE) ),
+ MAP(CTRL('n') , CMD(NEXT_LINE) ),
+ MAP(CTRL('d') , CMD(ERASE_AT) ),
+ MAP( DEL , CMD(ERASE_BEFORE)),
+ MAP(CTRL('a') , CMD(LINE_START) ),
+ MAP(CTRL('e') , CMD(LINE_END) ),
+ MAP(CTRL('k') , CMD(ERASE_REST) ),
+ MAP(CTRL('u') , CMD(ERASE_ALL) ),
+ MAP(CTRL('y') , CMD(YANK) ),
+ MAP(CTRL('m') , CMD(ENTER) ),
+ MAP(CTRL('l') , CMD(REDRAW) ),
+ MAP(CTRL('r') , CMD(SEARCH_BACK) ),
+ MAP(CTRL('s') , CMD(SEARCH_FORW) ),
+ MAP( ESC , BRL_TYPE_CSEQ ),
+};
+
+static extmap_t ext_map[] = {
+ { "\x1b[A" , 3, CMD(PREV_LINE) },
+ { "\x1b[B" , 3, CMD(NEXT_LINE) },
+ { "\x1b[C" , 3, CMD(FORWARD) },
+ { "\x1b[D" , 3, CMD(BACKWARD) },
+ { "\x1b[F" , 3, CMD(LINE_END) },
+ { "\x1b[H" , 3, CMD(LINE_START) },
+ { "\x1b[1;5A", 6, BRL_TYPE_INVALID },
+ { "\x1b[1;5B", 6, BRL_TYPE_INVALID },
+ { "\x1b[1;5C", 6, CMD(NEXT_WORD) },
+ { "\x1b[1;5D", 6, CMD(PREV_WORD) },
+ { NULL , 0, BRL_TYPE_INVALID }
+};
+
+
+/*
+ * ring buffer for saving history
+ */
+
+typedef struct {
+ char **entries; /* ring buffer entries */
+ int size; /* buffer size */
+ int next; /* buffer insertion point */
+ int srch; /* buffer search point */
+ char *pattern; /* search pattern buffer */
+ int psize; /* pattern buffer size */
+ int plen; /* actual pattern length */
+} ringbuf_t;
+
+
+
+/*
+ * breedline context
+ */
+
+struct brl_s {
+ int fd; /* file descriptor to read */
+ struct termios term_mode; /* original terminal settings */
+ int term_flags; /* terminal descriptor flags */
+ int term_blck; /* originally in blocking mode */
+ int term_ncol; /* number of columns */
+ void *ml; /* mainloop, if any */
+ brl_mainloop_ops_t *ml_ops; /* mainloop operations, if any */
+ void *ml_w; /* I/O watch */
+ brl_line_cb_t line_cb; /* input callback */
+ void *user_data; /* opaque callback data */
+ char *prompt; /* prompt string */
+ int hidden; /* whether prompt is hidden */
+ int mode; /* current mode */
+ unsigned char *buf; /* input buffer */
+ int size; /* size of the buffer */
+ int data; /* amount of data in buffer */
+ int offs; /* buffer insertion offset */
+ unsigned char *yank; /* yank buffer */
+ int yank_size; /* yank buffer size */
+ int yank_data; /* yank buffer effective length */
+ int *map; /* key map */
+ extmap_t *ext; /* extended key map */
+ int esc; /* CS being collected */
+ unsigned char seq[8]; /* control sequence buffer */
+ int seq_len; /* sequence length */
+ ringbuf_t h; /* history ring buffer */
+ char *saved; /* input buffer saved during search */
+ int dump; /* whether to dump key input */
+ char *dbg_buf; /* debug buffer */
+ int dbg_size; /* debug buffer size */
+ int dbg_len; /* debug buffer effective length */
+};
+
+
+static int setup_terminal(brl_t *brl);
+static int cleanup_terminal(brl_t *brl);
+static int terminal_size(int fd, int *nrow, int *ncol);
+static int enable_rawmode(brl_t *brl);
+static int restore_rawmode(brl_t *brl);
+static int disable_blocking(brl_t *brl);
+static int restore_blocking(brl_t *brl);
+
+static void process_input(brl_t *brl);
+static void reset_input(brl_t *brl);
+static void dump_input(brl_t *brl);
+static void redraw_prompt(brl_t *brl);
+
+static int ringbuf_init(ringbuf_t *rb, int size);
+static void ringbuf_purge(ringbuf_t *rb);
+
+static void *_brl_default_alloc(size_t size, const char *file, int line,
+ const char *func);
+static void *_brl_default_realloc(void *ptr, size_t size,
+ const char *file, int line, const char *func);
+static char *_brl_default_strdup(const char *str, const char *file, int line,
+ const char *func);
+static void _brl_default_free(void *ptr,
+ const char *file, int line, const char *func);
+
+
+
+brl_t *brl_create(int fd, const char *prompt)
+{
+ static int dump = -1, dbg_size = -1;
+ brl_t *brl;
+
+ if (dump < 0) {
+ const char *val = getenv("__BREEDLINE_DUMP_KEYS");
+ dump = (val != NULL && (*val == 'y' || *val == 'Y'));
+ }
+
+ if (dbg_size < 0) {
+ const char *val = getenv("__BREEDLINE_DEBUG");
+ dbg_size = (val == NULL ? 0 : atoi(val));
+ }
+
+ brl = brl_allocz(sizeof(*brl));
+
+ if (brl != NULL) {
+ brl->fd = fd;
+ brl->map = key_map;
+ brl->ext = ext_map;
+ brl->prompt = brl_strdup(prompt ? prompt : "");
+
+ brl->dump = dump;
+ brl->dbg_size = dbg_size;
+ if (dbg_size > 0)
+ brl->dbg_buf = brl_allocz(dbg_size);
+
+ brl_limit_history(brl, BRL_DEFAULT_HISTORY);
+
+ if (!setup_terminal(brl) && !terminal_size(fd, NULL, &brl->term_ncol))
+ return brl;
+ else
+ brl_destroy(brl);
+ }
+
+ return NULL;
+}
+
+
+void brl_destroy(brl_t *brl)
+{
+ if (brl != NULL) {
+ brl_hide_prompt(brl);
+ brl_free(brl->prompt);
+ brl_free(brl->buf);
+ brl_free(brl->saved);
+ brl_free(brl->yank);
+ brl_free(brl->dbg_buf);
+ ringbuf_purge(&brl->h);
+ cleanup_terminal(brl);
+
+ brl_free(brl);
+ }
+}
+
+
+int brl_set_prompt(brl_t *brl, const char *prompt)
+{
+ brl_free(brl->prompt);
+ brl->prompt = brl_strdup(prompt);
+
+ return (brl->prompt != NULL || prompt == NULL);
+}
+
+
+void brl_hide_prompt(brl_t *brl)
+{
+ static int warned = 0;
+ char buf[32];
+ int n, o;
+
+ brl->hidden = TRUE;
+
+ n = snprintf(buf, sizeof(buf), "%s%s", BRL_CURSOR_START, BRL_ERASE_RIGHT);
+ o = write(brl->fd, buf, n);
+ restore_rawmode(brl);
+
+ if (BRL_UNLIKELY(o < 0 && !warned)) { /* make gcc happy */
+ fprintf(stderr, "write to fd %d failed\n", brl->fd);
+ warned = 1;
+ }
+}
+
+
+void brl_show_prompt(brl_t *brl)
+{
+ brl->hidden = FALSE;
+ enable_rawmode(brl);
+ redraw_prompt(brl);
+}
+
+
+static void debug(brl_t *brl, const char *fmt, ...)
+{
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = vsnprintf(brl->dbg_buf, brl->dbg_size, fmt, ap);
+ va_end(ap);
+
+ if (n > 0)
+ brl->dbg_len = n < brl->dbg_size ? n : brl->dbg_size;
+ else
+ brl->dbg_len = 0;
+}
+
+
+static int ringbuf_init(ringbuf_t *rb, int size)
+{
+ int i;
+
+ for (i = 0; i < rb->size; i++)
+ brl_free(rb->entries[i]);
+
+ brl_free(rb->entries);
+ rb->entries = NULL;
+ rb->size = 0;
+ rb->next = 0;
+ rb->srch = 0;
+
+ brl_free(rb->pattern);
+ rb->pattern = NULL;
+ rb->psize = 0;
+ rb->plen = 0;
+
+ rb->entries = brl_allocz(size * sizeof(rb->entries[0]));
+
+ if (rb->entries == NULL && size != 0)
+ return -1;
+
+ rb->size = size;
+
+ return 0;
+}
+
+
+static void ringbuf_purge(ringbuf_t *rb)
+{
+ ringbuf_init(rb, 0);
+}
+
+
+static char **ringbuf_entry(ringbuf_t *rb, int idx)
+{
+ int i;
+
+ if (rb->entries == NULL) {
+ errno = ENOSPC;
+ return NULL;
+ }
+
+ if (idx >= rb->size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ if (idx < 0 && -idx > rb->size) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ if (idx == 0)
+ return rb->entries + rb->next;
+
+ if (idx < 0) {
+ i = rb->next + idx;
+
+ if (i < 0)
+ i += rb->size;
+
+ return rb->entries + i;
+ }
+ else {
+ i = rb->next - 1 + idx;
+
+ if (i >= rb->size)
+ i -= rb->size;
+
+ return rb->entries + i;
+ }
+}
+
+
+static int ringbuf_add(ringbuf_t *rb, const char *str)
+{
+ char **entry = ringbuf_entry(rb, 0);
+
+ if (entry != NULL) {
+ brl_free(*entry);
+ *entry = brl_strdup(str);
+ rb->next = (rb->next + 1) % rb->size;
+
+ return 0;
+ }
+ else
+ return -1;
+}
+
+
+static void ringbuf_reset_search(ringbuf_t *rb)
+{
+ rb->srch = 0;
+ rb->plen = 0;
+ memset(rb->pattern, 0, rb->psize);
+}
+
+
+static char *ringbuf_search(ringbuf_t *rb, int dir, unsigned char c,
+ brl_mode_t mode, char *current)
+{
+ int i;
+ char **e;
+
+ if (!c && mode == BRL_MODE_NORMAL) {
+ i = rb->srch + (dir < 0 ? -1 : +1);
+
+ if (i > 0)
+ return NULL;
+
+ if (i == 0) {
+ rb->srch = 0;
+ return current;
+ }
+
+ e = ringbuf_entry(rb, i);
+
+ if (e != NULL && *e != NULL) {
+ rb->srch = i;
+ return *e;
+ }
+ else
+ return NULL;
+ }
+ else if (mode == BRL_MODE_SEARCH_BACK) {
+ int total = rb->plen + 1;
+ int found = 0;
+
+ if (c) {
+ if (rb->psize == 0) {
+ rb->psize = 32;
+ if (!(rb->pattern = brl_allocz(rb->psize))) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ if (rb->psize < total) {
+ if (rb->psize * 2 > total)
+ total = rb->psize * 2;
+ if (!brl_reallocz(rb->pattern, rb->psize, total)) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ rb->psize = total;
+ }
+
+ rb->pattern[rb->plen++] = c;
+
+ i = rb->srch;
+ }
+ else {
+ /* keep searching backwards */
+ i = rb->srch - 1;
+ }
+
+ if (!rb->pattern) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* start searching backwards from current search point */
+
+ do {
+ e = ringbuf_entry(rb, i);
+
+ if (e != NULL && *e != NULL) {
+ /* pattern matching */
+ if (strstr(*e, rb->pattern)) {
+ found = 1;
+ break;
+ }
+ }
+
+ i--;
+ } while (e != NULL && !found);
+
+ if (found) {
+ /* set the search position while we're in search mode */
+ rb->srch = i;
+ return *e;
+ }
+
+ errno = ENOENT;
+ return NULL;
+ }
+
+ errno = EOPNOTSUPP;
+ return NULL;
+}
+
+
+int brl_limit_history(brl_t *brl, size_t size)
+{
+ return ringbuf_init(&brl->h, size);
+}
+
+
+int brl_add_history(brl_t *brl, const char *str)
+{
+ return ringbuf_add(&brl->h, str);
+}
+
+
+static int _brl_process_input(brl_t *brl)
+{
+ if (brl->dump)
+ dump_input(brl);
+ else
+ process_input(brl);
+
+ return 0;
+}
+
+
+int brl_read_line(brl_t *brl, char *buf, size_t size)
+{
+ if (brl->ml == NULL && brl->ml_ops == NULL) {
+ reset_input(brl);
+ enable_rawmode(brl);
+ brl_show_prompt(brl);
+ redraw_prompt(brl);
+ _brl_process_input(brl);
+
+ if (brl->data > 0)
+ snprintf(buf, size, "%s", brl->buf);
+ else
+ buf[0] = '\0';
+
+ brl_hide_prompt(brl);
+ restore_rawmode(brl);
+
+ return brl->data;
+ }
+ else {
+ errno = EINPROGRESS;
+ return -1;
+ }
+}
+
+
+static void _brl_io_cb(int fd, int events, void *user_data)
+{
+ brl_t *brl = (brl_t *)user_data;
+
+ BRL_UNUSED(fd);
+
+ if (events & POLLIN)
+ _brl_process_input(brl);
+
+ if (events & POLLHUP) {
+ brl->ml_ops->del_watch(brl->ml_w);
+ brl->ml_w = NULL;
+ }
+}
+
+
+int brl_use_mainloop(brl_t *brl, void *ml, brl_mainloop_ops_t *ops,
+ brl_line_cb_t cb, void *user_data)
+{
+ if (brl != NULL) {
+ if (brl->ml != NULL || brl->ml_ops != NULL) {
+ errno = EBUSY;
+ return -1;
+ }
+
+ brl->line_cb = cb;
+ brl->user_data = user_data;
+
+ brl->ml = ml;
+ brl->ml_ops = ops;
+ brl->ml_w = brl->ml_ops->add_watch(ml, brl->fd, _brl_io_cb, brl);
+
+ if (brl->ml_w != NULL) {
+ disable_blocking(brl);
+ return 0;
+ }
+ else
+ errno = EIO;
+ }
+ else
+ errno = EFAULT;
+
+ return -1;
+}
+
+
+static int enable_rawmode(brl_t *brl)
+{
+ struct termios mode;
+
+ memcpy(&mode, &brl->term_mode, sizeof(mode));
+
+ mode.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ mode.c_oflag &= ~OPOST;
+ mode.c_cflag |= CS8;
+ mode.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+
+ mode.c_cc[VMIN] = 1;
+ mode.c_cc[VTIME] = 0;
+
+ return tcsetattr(brl->fd, TCSAFLUSH, &mode);
+}
+
+
+static int restore_rawmode(brl_t *brl)
+{
+ return tcsetattr(brl->fd, TCSAFLUSH, &brl->term_mode);
+}
+
+
+static int disable_blocking(brl_t *brl)
+{
+ int flags;
+
+ if (brl->term_blck) {
+ brl->term_flags = fcntl(brl->fd, F_GETFL);
+
+ if (brl->term_flags != -1) {
+ flags = brl->term_flags | O_NONBLOCK;
+
+ if (fcntl(brl->fd, F_SETFL, flags) == 0) {
+ brl->term_blck = FALSE;
+ return 0;
+ }
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int restore_blocking(brl_t *brl)
+{
+ if (!brl->term_blck) {
+ if (fcntl(brl->fd, F_SETFL, brl->term_flags) == 0) {
+ brl->term_blck = FALSE;
+ return 0;
+ }
+ else
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int setup_terminal(brl_t *brl)
+{
+ if (!isatty(brl->fd)) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ if (tcgetattr(brl->fd, &brl->term_mode) == -1)
+ return -1;
+
+ if (enable_rawmode(brl) != 0)
+ return -1;
+
+ brl->term_flags = fcntl(brl->fd, F_GETFL);
+ brl->term_blck = TRUE;
+
+#if 0
+ if (disable_blocking(brl) == 0)
+ return 0;
+ else
+ return -1;
+#else
+ return 0;
+#endif
+}
+
+
+static int cleanup_terminal(brl_t *brl)
+{
+ return (restore_rawmode(brl) | restore_blocking(brl));
+}
+
+
+static int terminal_size(int fd, int *nrow, int *ncol)
+{
+ struct winsize ws;
+ int col, row;
+
+ if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
+ row = (ws.ws_row > 0 ? ws.ws_row : 80);
+ col = (ws.ws_col > 0 ? ws.ws_col : 25);
+
+ if (nrow != NULL)
+ *nrow = row;
+ if (ncol != NULL)
+ *ncol = col;
+
+ return 0;
+ }
+ else
+ return -1;
+}
+
+
+static void redraw_prompt(brl_t *brl)
+{
+ static int warned = 0;
+
+ char *prompt, *buf, *p;
+ int plen, dlen, space, start, trunc;
+ int l, n, o;
+ char search_buf[256];
+
+ if (brl->mode == BRL_MODE_SEARCH_BACK) {
+ snprintf(search_buf, 256, "search backwards: '%s'",
+ brl->h.pattern ? brl->h.pattern : "");
+ prompt = search_buf;
+ }
+ else {
+ prompt = brl->prompt ? brl->prompt : "";
+ }
+
+ plen = strlen(prompt) + 2; /* '> ' or '><' */
+
+ if (brl->dbg_len > 0)
+ plen += brl->dbg_len + 2;
+
+ space = brl->term_ncol - 1 - plen - 1; /* - 1 for potential trailing > */
+
+ /* adjust start if the cursor would be past the right edge */
+ if (brl->offs >= space)
+ start = brl->offs - space;
+ else
+ start = 0;
+
+ /* truncate if there's more data than fits the screen */
+ dlen = brl->data - start;
+
+ if (dlen > space) {
+ dlen = space;
+ trunc = TRUE;
+ }
+ else
+ trunc = FALSE;
+
+ l = plen + dlen + 64;
+ buf = alloca(l);
+ p = buf;
+
+#if 0
+ printf("\n\rplen = %d, dlen = %d, start = %d, buf = '%*.*s'\n\r",
+ plen, dlen, start, brl->data, brl->data, brl->buf);
+ printf("brl->offs = %d, effective offset = %d\n\r", brl->offs,
+ plen + brl->offs - start);
+#endif
+
+ /* position cursor to beginning of line */
+ n = snprintf(p, l, "%s", BRL_CURSOR_START);
+ p += n;
+ l -= n;
+
+ /* print prompt + visible portion of buffer */
+ n = snprintf(p, l, "%s%s%s%s%s%*.*s%s", prompt,
+ brl->dbg_len ? "[" : "",
+ brl->dbg_len ? brl->dbg_buf : "",
+ brl->dbg_len ? "]" : "",
+ start > 0 ? "><" : "> ",
+ dlen, dlen, brl->buf + start, trunc ? ">" : "");
+ p += n;
+ l -= n;
+
+ /* erase the rest of the line (ie. make sure there's no trailing junk) */
+ n = snprintf(p, l, "%s", BRL_ERASE_RIGHT);
+ p += n;
+ l -= n;
+
+ /* okay, perform the actions collected so far */
+ o = write(brl->fd, buf, (p - buf));
+
+ l = plen + dlen + 64;
+ p = buf;
+
+ if (BRL_UNLIKELY(o < 0 && !warned)) { /* make gcc happy */
+ fprintf(stderr, "write to fd %d failed\n", brl->fd);
+ warned = 1;
+ }
+
+ /* re-position cursor to the current insertion offset */
+ n = snprintf(p, l, BRL_CURSOR_START""BRL_CURSOR_FORW,
+ plen + brl->offs - start);
+ p += n;
+ l -= n;
+ o = write(brl->fd, buf, (p - buf));
+
+ if (BRL_UNLIKELY(o < 0 && !warned)) { /* make gcc happy */
+ fprintf(stderr, "write to fd %d failed\n", brl->fd);
+ warned = 1;
+ }
+}
+
+
+/*
+ * input buffer handling
+ */
+
+static void reset_input(brl_t *brl)
+{
+ memset(brl->buf, 0, brl->size);
+ brl->data = 0;
+ brl->offs = 0;
+}
+
+
+static int insert_input(brl_t *brl, const char *input, int len)
+{
+ int total;
+
+ total = brl->data + len + 1;
+
+ if (brl->size < total) {
+ if (brl->size * 2 > total)
+ total = brl->size * 2;
+ if (!brl_reallocz(brl->buf, brl->size, total))
+ return -1;
+ brl->size = total;
+ }
+
+ if (brl->offs < brl->data) {
+ memmove(brl->buf + brl->offs + len, brl->buf + brl->offs,
+ brl->data - brl->offs);
+ memcpy(brl->buf + brl->offs, input, len);
+ }
+ else
+ memcpy(brl->buf + brl->offs, input, len);
+
+ brl->data += len;
+ brl->offs += len;
+ brl->buf[brl->data] = '\0';
+
+ return 0;
+}
+
+
+static int erase_input(brl_t *brl, int n)
+{
+ if (n < 0) {
+ if (-n > brl->offs)
+ n = -brl->offs;
+ if (brl->offs < brl->data)
+ memmove(brl->buf + brl->offs + n, brl->buf + brl->offs,
+ brl->data - brl->offs);
+ brl->data += n;
+ if (brl->offs > brl->data)
+ brl->offs = brl->data;
+ }
+ else {
+ if (n > brl->data - brl->offs)
+ n = brl->data - brl->offs;
+ memmove(brl->buf + brl->offs, brl->buf + brl->offs + n,
+ brl->data - (brl->offs + n));
+ brl->data -= n;
+ }
+
+ return 0;
+}
+
+
+static void save_input(brl_t *brl)
+{
+ brl_free(brl->saved);
+ brl->saved = brl_strdup((char *)brl->buf);
+}
+
+
+static void save_yank(brl_t *brl, int start, int end)
+{
+ int size, len;
+
+ if (start < 0 || start >= brl->data || end > brl->data)
+ return;
+
+ len = end - start + 1;
+ size = len + 1;
+
+ if (brl->yank_size < size) {
+ if (brl->yank_size * 2 > size)
+ size = brl->yank_size * 2;
+ if (!brl_reallocz(brl->yank, brl->yank_size, size))
+ return;
+ }
+
+ brl->yank_size = size;
+
+ memcpy(brl->yank, brl->buf + start, len);
+ brl->yank[len] = '\0';
+ brl->yank_data = len - 1;
+}
+
+
+static void restore_input(brl_t *brl)
+{
+ reset_input(brl);
+
+ if (brl->saved != NULL) {
+ insert_input(brl, brl->saved, strlen(brl->saved));
+ brl_free(brl->saved);
+ brl->saved = NULL;
+ }
+}
+
+
+static int input_delimiter(brl_t *brl, int dir)
+{
+ static const char delim[] = " ,;:.?!'\"-_/";
+ char *s, *p;
+
+ if (brl->data == 0)
+ return 0;
+
+ if ((dir < 0 && brl->offs == 0) || (dir >= 0 && brl->offs >= brl->data))
+ return 0;
+
+ s = (char *)brl->buf + brl->offs;
+
+ if (dir < 0) {
+ p = s - 1;
+ if (p > (char *)brl->buf && strchr(delim, *p) != NULL)
+ p--;
+ while (p >= (char *)brl->buf) {
+ if (strchr(delim, *p) != NULL) {
+ p += 1;
+ break;
+ }
+ else
+ p--;
+ }
+ return p - s;
+ }
+ else {
+ p = s;
+ if (strchr(delim, *p) != NULL && s < (char *)brl->buf + brl->data)
+ p++;
+ while (p < (char *)brl->buf + brl->data) {
+ if (strchr(delim, *p) != NULL)
+ break;
+ else
+ p++;
+ }
+ return p - s;
+ }
+
+ return 0;
+}
+
+
+static void move_cursor(brl_t *brl, int n)
+{
+ brl->offs += n;
+
+ if (brl->offs < 0)
+ brl->offs = 0;
+ if (brl->offs > brl->data)
+ brl->offs = brl->data;
+}
+
+
+static void bell(brl_t *brl)
+{
+ int fd;
+
+ if (brl->fd == fileno(stdin))
+ fd = fileno(stderr);
+ else
+ fd = brl->fd;
+
+ dprintf(fd, "%c", BELL);
+}
+
+
+/*
+ * input mapping
+ */
+
+static int map_input(brl_t *brl, unsigned char c)
+{
+ int mapped;
+
+ mapped = brl->map[c];
+
+ if (mapped == BRL_TYPE_SELF)
+ return BRL_TAG_INPUT(BRL_TYPE_SELF, c);
+ else
+ return mapped;
+}
+
+
+static int map_esc_sequence(brl_t *brl)
+{
+ BRL_UNUSED(brl);
+
+ return BRL_TYPE_INVALID;
+}
+
+
+static int map_ctrl_sequence(brl_t *brl)
+{
+ extmap_t *e;
+ int d;
+
+ if (brl->ext != NULL) {
+ for (e = brl->ext; e->seq != NULL; e++) {
+ if (e->len == brl->seq_len) {
+ d = strncmp((char *)brl->seq, e->seq, e->len);
+
+ if (d == 0)
+ return e->key;
+
+ if (d < 0)
+ break;
+ }
+
+ if (e->len > brl->seq_len)
+ break;
+ }
+ }
+
+ return BRL_TYPE_INVALID;
+}
+
+
+/*
+ * main input processing
+ */
+
+static void process_input(brl_t *brl)
+{
+ unsigned char c;
+ int mapped, type, in, n, diff;
+ char out, *line, *hentry;
+
+ while((n = read(brl->fd, &c, sizeof(c))) > 0) {
+ if (brl->esc) {
+ if (brl->seq_len < (int)sizeof(brl->seq))
+ brl->seq[brl->seq_len++] = c;
+
+ if (brl->seq_len == 2) {
+ if (c != '[') {
+ mapped = map_esc_sequence(brl);
+ brl->esc = FALSE;
+ }
+ else
+ continue;
+ }
+ else {
+ if (0x40 <= c && c <= 0x7e) {
+ mapped = map_ctrl_sequence(brl);
+ brl->esc = FALSE;
+ }
+ else {
+ if (brl->seq_len == (int)sizeof(brl->seq)) {
+ mapped = BRL_TYPE_INVALID;
+ brl->esc = FALSE;
+ }
+ else
+ continue;
+ }
+ }
+ }
+ else
+ mapped = map_input(brl, c);
+
+ type = BRL_INPUT_TYPE(mapped);
+ in = BRL_INPUT_DATA(mapped);
+
+ switch (type) {
+ case BRL_TYPE_SELF:
+ switch (brl->mode) {
+ case BRL_MODE_NORMAL:
+ out = (char)(in & 0xff);
+ insert_input(brl, &out, 1);
+ redraw_prompt(brl);
+ break;
+ case BRL_MODE_SEARCH_BACK:
+ out = (char)(in & 0xff);
+ hentry = ringbuf_search(&brl->h, 0, out, BRL_MODE_SEARCH_BACK, NULL);
+ if (hentry != NULL) {
+ reset_input(brl);
+ insert_input(brl, hentry, strlen(hentry));
+ }
+ else
+ bell(brl);
+ redraw_prompt(brl);
+ break;
+ case BRL_MODE_SEARCH_FORW:
+ /* TODO */
+ break;
+ }
+ break;
+
+ case BRL_TYPE_COMMAND:
+ switch (in) {
+ case BRL_CMD_PREV_LINE:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ if (brl->h.srch == 0)
+ save_input(brl);
+ hentry = ringbuf_search(&brl->h, -1, 0, BRL_MODE_NORMAL, (char *)brl->saved);
+ debug(brl, "s:%d,'%s'", brl->h.srch,
+ brl->saved ? brl->saved : "-");
+ if (hentry != NULL) {
+ reset_input(brl);
+ insert_input(brl, hentry, strlen(hentry));
+ redraw_prompt(brl);
+ }
+ else
+ bell(brl);
+ break;
+
+ case BRL_CMD_NEXT_LINE:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ hentry = ringbuf_search(&brl->h, +1, 0, BRL_MODE_NORMAL, (char *)brl->saved);
+ debug(brl, "s:%d,'%s'", brl->h.srch,
+ brl->saved ? brl->saved : "-");
+ if (hentry != NULL) {
+ if (hentry == brl->saved)
+ restore_input(brl);
+ else {
+ reset_input(brl);
+ insert_input(brl, hentry, strlen(hentry));
+ }
+ redraw_prompt(brl);
+ }
+ else
+ bell(brl);
+ break;
+
+ case BRL_CMD_SEARCH_BACK:
+ if (brl->mode == BRL_MODE_SEARCH_BACK) {
+ /* already in search mode, continue */
+ hentry = ringbuf_search(&brl->h, 0, 0, BRL_MODE_SEARCH_BACK, NULL);
+ if (hentry != NULL) {
+ reset_input(brl);
+ insert_input(brl, hentry, strlen(hentry));
+ }
+ else
+ bell(brl);
+ }
+ else {
+ if (brl->h.srch == 0)
+ save_input(brl);
+ brl->mode = BRL_MODE_SEARCH_BACK;
+ }
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_BACKWARD:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ move_cursor(brl, -1);
+ redraw_prompt(brl);
+ break;
+ case BRL_CMD_FORWARD:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ move_cursor(brl, +1);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_LINE_START:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ move_cursor(brl, -brl->offs);
+ redraw_prompt(brl);
+ break;
+ case BRL_CMD_LINE_END:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ move_cursor(brl, brl->data - brl->offs);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_ERASE_BEFORE:
+ switch(brl->mode) {
+ case BRL_MODE_NORMAL:
+ erase_input(brl, -1);
+ if (brl->offs < brl->data)
+ move_cursor(brl, -1);
+ redraw_prompt(brl);
+ break;
+ case BRL_MODE_SEARCH_BACK:
+ case BRL_MODE_SEARCH_FORW:
+ if (brl->h.plen > 0) {
+ brl->h.pattern[--brl->h.plen] = '\0';
+ }
+ else {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ restore_input(brl);
+ }
+ redraw_prompt(brl);
+ break;
+ }
+ break;
+ case BRL_CMD_ERASE_AT:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ erase_input(brl, 1);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_ERASE_REST:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ save_yank(brl, brl->offs, brl->data);
+ erase_input(brl, brl->data - brl->offs);
+ redraw_prompt(brl);
+ break;
+ case BRL_CMD_ERASE_ALL:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ save_yank(brl, 0, brl->data);
+ reset_input(brl);
+ redraw_prompt(brl);
+ break;
+ case BRL_CMD_YANK:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ insert_input(brl, (char *)brl->yank, brl->yank_data);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_PREV_WORD:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ diff = input_delimiter(brl, -1);
+ move_cursor(brl, diff);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_NEXT_WORD:
+ if (brl->mode != BRL_MODE_NORMAL) {
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ }
+ diff = input_delimiter(brl, +1);
+ move_cursor(brl, diff);
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_REDRAW:
+ redraw_prompt(brl);
+ break;
+
+ case BRL_CMD_ENTER:
+ dprintf(brl->fd, "\n\r");
+ if (brl->line_cb != NULL) {
+ line = alloca(brl->data + 1);
+ strncpy(line, (char *)brl->buf, brl->data);
+ line[brl->data] = '\0';
+ reset_input(brl);
+ restore_rawmode(brl);
+ brl->line_cb(brl, line, brl->user_data);
+ enable_rawmode(brl);
+ ringbuf_reset_search(&brl->h);
+ brl->mode = BRL_MODE_NORMAL;
+ debug(brl, "");
+ redraw_prompt(brl);
+ }
+ else
+ return;
+ break;
+
+ default:
+#if 0
+ printf("editing command 0x%x\n\r", in);
+#endif
+ bell(brl);
+ }
+ break;
+
+ case BRL_TYPE_CSEQ:
+ brl->esc = TRUE;
+ brl->seq[0] = c;
+ brl->seq_len = 1;
+ break;
+
+ case BRL_TYPE_INVALID:
+ default:
+ bell(brl);
+ break;
+ }
+ }
+}
+
+
+static void dump_input(brl_t *brl)
+{
+ unsigned char c, seq[64], s[4] = " \0";
+ int i = 0;
+
+ printf("got input:");
+
+ while (read(brl->fd, &c, 1) == 1) {
+ printf(" 0x%2.2x", c);
+ seq[i++] = c;
+
+ if (c == 0x3)
+ exit(0);
+ }
+
+ printf("\n\r");
+ seq[i] = '\0';
+
+ printf(" ");
+ for (i = 0; seq[i] != 0; i++) {
+ printf(" %4d", seq[i]);
+ }
+ printf("\n\r");
+
+ seq[i] = '\0';
+ printf(" ");
+ for (i = 0; seq[i] != '\0'; i++) {
+ s[3] = c = seq[i];
+ printf(" %s", (isprint(c) && c != '\n' && c != '\r' && c != '\t') ?
+ (char *)s : (c == ESC ? "ESC" : "."));
+ }
+ printf("\n\r");
+}
+
+
+/*
+ * default passthru allocator
+ */
+
+static void *_brl_default_alloc(size_t size, const char *file, int line,
+ const char *func)
+{
+ BRL_UNUSED(file);
+ BRL_UNUSED(line);
+ BRL_UNUSED(func);
+
+ return malloc(size);
+}
+
+static void *_brl_default_realloc(void *ptr, size_t size,
+ const char *file, int line, const char *func)
+{
+ BRL_UNUSED(file);
+ BRL_UNUSED(line);
+ BRL_UNUSED(func);
+
+ return realloc(ptr, size);
+}
+
+static char *_brl_default_strdup(const char *str, const char *file, int line,
+ const char *func)
+{
+ BRL_UNUSED(file);
+ BRL_UNUSED(line);
+ BRL_UNUSED(func);
+
+ return strdup(str);
+}
+
+static void _brl_default_free(void *ptr,
+ const char *file, int line, const char *func)
+{
+ BRL_UNUSED(file);
+ BRL_UNUSED(line);
+ BRL_UNUSED(func);
+
+ free(ptr);
+}
+
+
+/* By default we use the libc memory allocator. */
+brl_allocator_t __brl_mm = {
+ .allocfn = _brl_default_alloc,
+ .reallocfn = _brl_default_realloc,
+ .strdupfn = _brl_default_strdup,
+ .freefn = _brl_default_free
+};
+
+/* Once an allocation is done, this will block changing the allocator. */
+int __brl_mm_busy = FALSE;
+
+
+int brl_set_allocator(brl_allocator_t *allocator)
+{
+ if (!__brl_mm_busy) {
+ __brl_mm = *allocator;
+
+ return 0;
+ }
+ else {
+ errno = EBUSY;
+
+ return -1;
+ }
+}