aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/rc/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/rc/input.c')
-rw-r--r--src/cmd/rc/input.c1679
1 files changed, 1679 insertions, 0 deletions
diff --git a/src/cmd/rc/input.c b/src/cmd/rc/input.c
new file mode 100644
index 0000000..cc2383d
--- /dev/null
+++ b/src/cmd/rc/input.c
@@ -0,0 +1,1679 @@
+#include "rc.h"
+
+#include <termios.h>
+#include <sys/ioctl.h>
+
+/* don't change order of these without modifying matrix */
+enum
+{
+ NonPrintable,
+ Alnum,
+ Punctuation,
+ Space
+};
+
+static int ascii[256] =
+{
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1,
+ 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+struct Mode
+{
+ ushort raw : 1;
+ ushort multiline : 1;
+ ushort mask : 1;
+ ushort defer : 1;
+ struct {
+ ushort on : 1;
+ ushort insert : 1;
+ } vi ;
+};
+
+/*
+ * the structure represents the state during line editing.
+ * we pass this state to functions implementing specific editing functionalities
+ */
+struct TerminalState
+{
+ int ifd; /* terminal stdin file descriptor. */
+ int ofd; /* terminal stdout file descriptor. */
+
+ struct{
+ char *s; /* raw UTF-8 bytes */
+ int len; /* number of bytes in prompt */
+ int size; /* number of (printed) runes in prompt */
+ } prompt;
+
+ struct{
+ intptr cap; /* capacity of edit buffer */
+ intptr len; /* current number of bytes stored */
+ intptr pos; /* position within edit buffer */
+ char *buf;
+ } edit; /* edit buffer */
+
+ struct{
+ intptr cap; /* number of columns in terminal */
+ intptr len; /* current edited line length (in runes) */
+ intptr pos; /* current cursor position (in runes) */
+ intptr old; /* previous refresh cursor position (in runes) */
+ } cursor;
+
+ struct{
+ intptr cap;
+ intptr len;
+ char *buf;
+ } yank; /* yank buffer */
+
+ intptr maxrows; /* maximum num of rows used so far (multiline mode) */
+ intptr history; /* index of history we are currently editing */
+};
+
+/*
+ * line history (circular buffer)
+ */
+struct History
+{
+ char **bot, **top, *entry[1024];
+};
+
+/* globals */
+static struct Mode mode;
+static struct History history;
+static struct termios originalterm;
+
+enum
+{
+ KeyNil = 0, /* nil */
+ KeyCtrlA = 1, /* Ctrl+a */
+ KeyCtrlB = 2, /* Ctrl-b */
+ KeyCtrlC = 3, /* Ctrl-c */
+ KeyCtrlD = 4, /* Ctrl-d */
+ KeyCtrlE = 5, /* Ctrl-e */
+ KeyCtrlF = 6, /* Ctrl-f */
+ KeyCtrlH = 8, /* Ctrl-h */
+ KeyTab = 9, /* Tab */
+ KeyCtrlK = 11, /* Ctrl+k */
+ KeyCtrlL = 12, /* Ctrl+l */
+ KeyEnter = 13, /* Enter */
+ KeyCtrlN = 14, /* Ctrl-n */
+ KeyCtrlP = 16, /* Ctrl-p */
+ KeyCtrlT = 20, /* Ctrl-t */
+ KeyCtrlU = 21, /* Ctrl+u */
+ KeyCtrlW = 23, /* Ctrl+w */
+ KeyEsc = 27, /* Escape */
+ KeyBackspace = 127 /* Backspace */
+};
+
+static void doatexit(void);
+
+/* vi operations */
+typedef struct
+{
+ intptr buffer;
+ intptr cursor;
+} Position;
+
+typedef Position (*Noun)(struct TerminalState*, int);
+typedef void (*Verb)(struct TerminalState*, Position);
+
+static
+int
+runetype(rune r)
+{
+ if(r<128)
+ return ascii[r];
+ if(utf8·isspace(r))
+ return Space;
+ if(utf8·isdigit(r) || utf8·isalpha(r))
+ return Alnum;
+ if(utf8·ispunct(r))
+ return Punctuation;
+
+ return NonPrintable;
+}
+
+static
+void
+normalcursor(int fd)
+{
+ write(fd,"\e[2 q",5);
+}
+
+static
+void
+insertcursor(int fd)
+{
+ write(fd,"\e[6 q",5);
+}
+
+/* raw mode: 1960 magic shit. */
+static
+int
+enterraw(int fd)
+{
+ struct termios raw;
+
+ if(!shell.interactive)
+ goto fatal;
+
+ if(!mode.defer){
+ atexit(doatexit);
+ mode.defer = 1;
+ }
+ if(tcgetattr(fd,&originalterm) == -1)
+ goto fatal;
+
+ raw = originalterm; /* modify the original mode */
+
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ /* output modes - disable post processing */
+ raw.c_oflag &= ~(OPOST);
+ /* control modes - set 8 bit chars */
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z,^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+ /* put terminal in raw mode after flushing */
+ if(tcsetattr(fd,TCSAFLUSH,&raw) < 0)
+ goto fatal;
+
+ mode.raw = 1;
+ return 1;
+
+fatal:
+ errno = ENOTTY;
+ return 0;
+}
+
+static
+void
+exitraw(int fd)
+{
+ /* don't even check the return value as it's too late. */
+ if(mode.raw && tcsetattr(fd,TCSAFLUSH,&originalterm) != -1)
+ mode.raw = 0;
+}
+
+/* use the esc [6n escape sequence to query the horizontal cursor position
+ * and return it. on error -1 is returned, on success the position of the
+ * cursor. */
+static
+int
+cursorposition(int ifd, int ofd)
+{
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ /* Report cursor location */
+ if(write(ofd, "\x1b[6n", 4) != 4)
+ return -1;
+
+ /* Read the response: ESC [ rows ; cols R */
+ while(i < sizeof(buf)-1) {
+ if(read(ifd,buf+i,1) != 1)
+ break;
+ if(buf[i] == 'R')
+ break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ /* Parse it. */
+ if(buf[0] != KeyEsc || buf[1] != '[')
+ return -1;
+ if(sscanf(buf+2,"%d;%d",&rows,&cols) != 2)
+ return -1;
+
+ return cols;
+}
+
+/* try to get the number of columns in the current terminal, or assume 80 if it fails. */
+static
+int
+columns(int ifd, int ofd)
+{
+ struct winsize ws;
+
+ if(ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0){
+ /* ioctl() failed. Try to query the terminal itself. */
+ int start, cols;
+
+ /* Get the initial position so we can restore it later. */
+ start = cursorposition(ifd,ofd);
+ if(start == -1)
+ goto failed;
+
+ /* Go to right margin and get position. */
+ if(write(ofd,"\x1b[999C",6) != 6)
+ goto failed;
+ cols = cursorposition(ifd,ofd);
+ if(cols == -1)
+ goto failed;
+
+ /* Restore position. */
+ if(cols > start){
+ char esc[32];
+ snprintf(esc,32,"\x1b[%dD",cols-start);
+ if(write(ofd,esc,strlen(esc)) == -1)
+ ;
+ }
+ return cols;
+ }else
+ return ws.ws_col;
+
+failed:
+ return 80;
+}
+
+static
+void
+clear(void)
+{
+ if(write(1,"\x1b[H\x1b[2J",7) <= 0)
+ ;
+}
+
+/* beep: used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+static
+void
+beep(void)
+{
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+
+// -----------------------------------------------------------------------
+// command history
+
+void
+inithistory(void)
+{
+ history.bot = history.top = history.entry;
+}
+
+int
+addhistory(char *line)
+{
+ char *copy;
+
+ copy = strdup(line);
+ if(!copy)
+ return 0;
+
+ *history.top++ = copy;
+ if(history.top == arrend(history.entry))
+ history.top = history.entry;
+
+ if(history.top == history.bot){
+ efree(history.bot);
+ history.bot++;
+ }
+
+ return 1;
+}
+
+static
+void
+pophistory(void)
+{
+ if(--history.top < history.entry)
+ history.top = arrend(history.entry)-1;
+ efree(*history.top);
+}
+
+static void refreshline(struct TerminalState *);
+
+static
+char **
+currenthistory(struct TerminalState *term, intptr *size)
+{
+ char **entry;
+ intptr len, head;
+
+ if(history.top > history.bot){
+ len = history.top - history.bot;
+ entry = history.top - term->history - 1;
+ }else if(history.top < history.bot){
+ len = (arrend(history.entry) - history.bot) + (history.top - history.entry);
+ if((head=history.top - history.entry) < term->history)
+ entry = arrend(history.entry) - head;
+ else
+ entry = history.top - term->history - 1;
+ }else
+ return nil;
+
+ *size = len;
+ return entry;
+}
+
+static
+void
+usehistory(struct TerminalState *term, int d)
+{
+ rune r;
+ intptr w, len;
+ char *b, *e, **entry;
+
+ if(!(entry = currenthistory(term, &len)))
+ return;
+
+ efree(*entry);
+ *entry = strdup(term->edit.buf);
+
+ term->history += d;
+ if(term->history < 0){
+ term->history = 0;
+ return;
+ }else if(term->history >= len){
+ term->history = len - 1;
+ return;
+ }
+ entry = currenthistory(term, &len);
+
+ strncpy(term->edit.buf, *entry, term->edit.cap);
+ term->edit.buf[term->edit.cap-1] = 0;
+
+ /* update cursor/buffer positions */
+ term->edit.len = term->edit.pos = strlen(term->edit.buf);
+ for(w=0, b=term->edit.buf, e=term->edit.buf+term->edit.len; b < e; ){
+ b += utf8·decode(b, &r);
+ w += utf8·runewidth(r);
+ }
+ term->cursor.len = term->cursor.pos = w;
+
+ refreshline(term);
+}
+
+// -----------------------------------------------------------------------
+// line editing
+
+/*
+ * we define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. this is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects.
+ */
+
+struct Buffer
+{
+ int len;
+ char *b;
+};
+
+static
+void
+initbuffer(struct Buffer *ab)
+{
+ ab->b = nil;
+ ab->len = 0;
+}
+
+static
+void
+append(struct Buffer *ab, const char *s, int len)
+{
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == nil) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static
+void
+freebuffer(struct Buffer *ab)
+{
+ free(ab->b);
+}
+
+/* single line low level line refresh.
+ *
+ * rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static
+void
+refreshsingleline(struct TerminalState *term)
+{
+ char esc[64];
+ struct Buffer ab;
+
+ int n, w;
+ rune r;
+ int fd = term->ofd;
+ intptr off = term->prompt.size;
+ char *buf = term->edit.buf;
+ intptr len = term->edit.len;
+ intptr pos = term->cursor.pos;
+ intptr col = term->cursor.len;
+
+ while((off+pos) >= term->cursor.cap){
+ n = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+
+ buf+=n, len-=n;
+ pos-=w, col-=w;
+ }
+
+ assert(buf <= term->edit.buf + len);
+
+ while(off+col > term->cursor.cap){
+ n = utf8·decodeprev(buf+len-1, &r);
+ w = utf8·runewidth(r);
+
+ len-=n, col-=w;
+ }
+ assert(len >= 0);
+
+ initbuffer(&ab); // TODO: do we need so much malloc pressure?
+
+ /* move cursor to left edge */
+ snprintf(esc,64,"\r");
+ append(&ab,"\r",1);
+
+ /* write the prompt and the current buffer content */
+ append(&ab, term->prompt.s, term->prompt.len);
+
+ if(mode.mask == 1)
+ while(len--)
+ append(&ab,"*",1);
+ else
+ append(&ab,buf,len);
+
+ snprintf(esc,64,"\x1b[0K"); // erase to right
+ append(&ab,esc,strlen(esc));
+
+ snprintf(esc,64,"\r\x1b[%dC", (int)(off+pos)); // move cursor to original position
+ append(&ab,esc,strlen(esc));
+
+ if(write(fd,ab.b,ab.len) == -1) /* can't recover from write error. */
+ ;
+
+ freebuffer(&ab);
+}
+
+/* multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static
+void
+refreshmultilines(struct TerminalState *term)
+{
+#if 0
+ char esc[64];
+ int plen = term->plen;
+ int rows = (plen+term->len+term->cols-1)/term->cols; /* rows used by current buf. */
+ int rpos = (plen+term->oldpos+term->cols)/term->cols; /* cursor relative row. */
+ int rpos2; /* rpos after refresh. */
+ int col; /* colum position, zero-based. */
+ int i;
+ int old_rows = term->maxrows;
+ int fd = term->ofd, j;
+ struct Buffer ab;
+
+ /* Update maxrows if needed. */
+ if(rows > (int)term->maxrows)
+ term->maxrows = rows;
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ initbuffer(&ab);
+ if(old_rows-rpos > 0){
+ snprintf(esc,64,"\x1b[%dB", old_rows-rpos);
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* Now for every row clear it, go up. */
+ for(j = 0; j < old_rows-1; j++){
+ snprintf(esc,64,"\r\x1b[0K\x1b[1A");
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* clean the top line. */
+ snprintf(esc,64,"\r\x1b[0K");
+ append(&ab,esc,strlen(esc));
+
+ /* Write the prompt and the current buffer content */
+ append(&ab,term->prompt,strlen(term->prompt));
+ if(mode.mask == 1){
+ for(i = 0; i < term->len; i++) append(&ab,"*",1);
+ }else
+ append(&ab,term->buf,term->len);
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if(term->pos && term->pos == term->len && (term->pos+plen) % term->cols == 0) {
+ append(&ab,"\n",1);
+ snprintf(esc,64,"\r");
+ append(&ab,esc,strlen(esc));
+ rows++;
+ if(rows > (int)term->maxrows)
+ term->maxrows = rows;
+ }
+
+ /* Move cursor to right position. */
+ rpos2 = (plen+term->pos+term->cols)/term->cols; /* current cursor relative row. */
+
+ /* Go up till we reach the expected positon. */
+ if(rows-rpos2 > 0){
+ snprintf(esc,64,"\x1b[%dA", rows-rpos2);
+ append(&ab,esc,strlen(esc));
+ }
+
+ /* Set column. */
+ col = (plen+(int)term->pos) % (int)term->cols;
+ if(col)
+ snprintf(esc,64,"\r\x1b[%dC", col);
+ else
+ snprintf(esc,64,"\r");
+ append(&ab,esc,strlen(esc));
+
+ term->oldpos = term->pos;
+
+ if(write(fd,ab.b,ab.len) == -1) /* Can't recover from write error. */
+ ;
+
+ freebuffer(&ab);
+#endif
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+static
+void
+refreshline(struct TerminalState *term)
+{
+ if(mode.multiline)
+ refreshmultilines(term);
+ else
+ refreshsingleline(term);
+}
+
+/* insert the rune 'c' at cursor current position.
+ * on error writing to the terminal -1 is returned, otherwise 0. */
+int
+insertrune(struct TerminalState *term, int n, char *c)
+{
+ int w;
+ rune r;
+
+ utf8·decode(c, &r);
+ w = utf8·runewidth(r);
+
+ if(term->edit.len + n <= term->edit.cap){
+ if(term->edit.pos == term->edit.len){
+ memcpy(term->edit.buf+term->edit.pos, c, n);
+
+ term->edit.pos += n, term->edit.len += n;
+ term->cursor.pos += w, term->cursor.len += w;
+
+ term->edit.buf[term->edit.len] = '\0';
+
+ if(!mode.multiline && ((term->prompt.size+term->cursor.pos+n) <= term->cursor.cap)){
+ if(mode.mask){
+ c = "*";
+ n = 1;
+ }
+ if(write(term->ofd, c, n) == -1)
+ return 0;
+ }
+ refreshline(term);
+ }else{
+ memmove(term->edit.buf+term->edit.pos+n, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos);
+ memcpy(term->edit.buf+term->edit.pos, c, n);
+
+ term->edit.pos += n, term->edit.len += n;
+ term->cursor.pos += w, term->cursor.len += w;
+
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }
+ }
+
+ return 1;
+}
+
+int
+insertbytes(struct TerminalState *term, int len, char *buf)
+{
+ int nr;
+ if(term->edit.len + len > term->edit.cap){
+ len = term->edit.cap - term->edit.len;
+ buf[len] = 0;
+ }
+ nr = utf8·len(buf);
+
+ if(term->edit.pos == term->cursor.len){
+ memcpy(term->edit.buf+term->edit.len, buf, len);
+
+ term->edit.pos += len, term->edit.len += len;
+ term->cursor.pos += nr, term->cursor.len += nr;
+
+ // XXX: transfer the modeline here?
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }else{
+ memmove(term->edit.buf+term->edit.pos+len,term->edit.buf+term->edit.pos,term->edit.len-term->edit.pos);
+ memcpy(term->edit.buf+term->edit.pos, buf, len);
+
+ term->edit.pos += len, term->edit.len += len;
+ term->cursor.pos += nr, term->cursor.len += nr;
+
+ term->edit.buf[term->edit.len] = '\0';
+ refreshline(term);
+ }
+
+ return 1;
+}
+
+// -----------------------------------------------------------------------
+// vi functionality
+
+/* modes */
+
+static
+void
+normalmode(int fd)
+{
+ mode.vi.insert = 0;
+ normalcursor(fd);
+}
+
+static
+void
+insertmode(int fd)
+{
+ mode.vi.insert = 1;
+ insertcursor(fd);
+}
+
+/* actions */
+
+static
+void
+move(struct TerminalState *term, Position to)
+{
+ if(to.buffer != term->edit.pos){
+ term->edit.pos = to.buffer;
+ term->cursor.pos = to.cursor;
+ refreshline(term);
+ }
+}
+
+static
+void
+yank(struct TerminalState *term, Position to)
+{
+ intptr len, off;
+
+ if(to.buffer == term->edit.pos)
+ return; // noop
+
+ if(to.buffer > term->edit.pos){
+ len = to.buffer - term->edit.pos;
+ off = term->edit.pos;
+ }else{
+ len = term->edit.pos - to.buffer;
+ off = to.buffer;
+ }
+
+ if(term->yank.cap < len+1){
+ efree(term->yank.buf);
+ term->yank.cap = len+1;
+ term->yank.buf = emalloc(len+1);
+ }
+ term->yank.len = len;
+ memcpy(term->yank.buf, term->edit.buf+off, len);
+ term->yank.buf[len] = 0;
+}
+
+static
+void
+delete(struct TerminalState *term, Position to)
+{
+ intptr diff;
+
+ // delete characters in front of us (exclusive)
+ if(to.buffer > term->edit.pos){
+ diff = to.buffer - term->edit.pos;
+ memmove(term->edit.buf+term->edit.pos, term->edit.buf+to.buffer, term->edit.len-to.buffer+1);
+ term->edit.len -= diff;
+
+ diff = to.cursor - term->cursor.pos;
+ goto refresh;
+ }
+
+ // delete characters behind us
+ if(to.buffer < term->edit.pos){
+ diff = term->edit.pos - to.buffer;
+ memmove(term->edit.buf+to.buffer, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos+1);
+ term->edit.pos = to.buffer;
+ term->edit.len -= diff;
+
+ diff = term->cursor.pos - to.cursor;
+ term->cursor.pos = to.cursor;
+ goto refresh;
+ }
+ // do nothing
+ return;
+
+refresh:
+ term->cursor.len -= diff;
+ refreshline(term);
+}
+/* movements */
+
+#define CURRENT(term) (Position){ .buffer=(term)->edit.pos, .cursor=(term)->cursor.pos };
+
+// move cursor to the left n boxes
+static
+Position
+left(struct TerminalState *term, int n)
+{
+ rune r;
+ int w, d;
+ Position pos = CURRENT(term);
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n > 0 && buf > term->edit.buf){
+ buf -= utf8·decodeprev(buf-1, &r);
+
+ w = utf8·runewidth(r);
+ n -= w;
+ d += w;
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+// move cursor to the right n boxes
+static
+Position
+right(struct TerminalState *term, int n)
+{
+ rune r;
+ int w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n > 0 && buf < end){
+ buf += utf8·decode(buf, &r);
+
+ w = utf8·runewidth(r);
+ n -= w;
+ d += w;
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+prevword(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, w, b, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n-- > 0 && buf > term->edit.buf){
+ eatspace:
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+ if((c=runetype(r)) == Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if(runetype(r) == c){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+static
+Position
+nextword(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf < end){
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ c = runetype(r);
+ eatword:
+ if(runetype(r) == c){
+ buf += b;
+ d += w;
+
+ if(buf >= end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ goto eatword;
+ }
+ eatspace:
+ while((c=runetype(r)) == Space){
+ buf += b;
+ d += w;
+
+ if(buf >= end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ goto eatspace;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+
+static
+Position
+prevWord(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, w, b, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+
+ d = 0;
+ while(n-- > 0 && buf > term->edit.buf){
+ eatspace:
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+ if((c=runetype(r)) == Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if((c=runetype(r)) != Space){
+ buf -= b;
+ d += w;
+
+ if(buf <= term->edit.buf)
+ break;
+
+ b = utf8·decodeprev(buf-1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MAX(pos.cursor-d, 0);
+ pos.buffer = MAX(buf-term->edit.buf, 0);
+ return pos;
+}
+
+static
+Position
+nextWord(struct TerminalState *term, int n)
+{
+ rune r;
+ int b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf < end){
+ eatword:
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+ if(runetype(r) != Space){
+ buf += b;
+ d += w;
+
+ if(buf > end)
+ break;
+
+ goto eatword;
+ }
+
+ eatspace:
+ if(runetype(r) == Space){
+ buf += b;
+ d += w;
+
+ if(buf > end)
+ break;
+
+ b = utf8·decode(buf, &r);
+ w = utf8·runewidth(r);
+
+ goto eatspace;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+nextend(struct TerminalState *term, int n)
+{
+ rune r;
+ int c, b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf+1 < end){
+ eatspace:
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ while((c=runetype(r)) == Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 >= end)
+ break;
+
+ goto eatspace;
+ }
+ eatword:
+ if(runetype(r) == c){
+ buf += b;
+ d += w;
+
+ if(buf+1 >= end)
+ break;
+
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+static
+Position
+nextEnd(struct TerminalState *term, int n)
+{
+ rune r;
+ int b, w, d;
+ Position pos = CURRENT(term);
+
+ char *buf = term->edit.buf + term->edit.pos;
+ char *end = term->edit.buf + term->edit.len;
+
+ d = 0;
+ while(n-- > 0 && buf+1 < end){
+ eatspace:
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+ if(runetype(r) == Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 > end)
+ break;
+
+ goto eatspace;
+ }
+
+ eatword:
+ if(runetype(r) != Space){
+ buf += b;
+ d += w;
+
+ if(buf+1 > end)
+ break;
+
+ b = utf8·decode(buf+1, &r);
+ w = utf8·runewidth(r);
+
+ goto eatword;
+ }
+ }
+
+ pos.cursor = MIN(pos.cursor+d, term->cursor.len);
+ pos.buffer = MIN(buf-term->edit.buf, term->edit.len);
+ return pos;
+}
+
+#define HOME(term) (Position){0}
+#define END(term) (Position){(term)->edit.len, (term)->cursor.len}
+
+static
+int
+vi(struct TerminalState *term, char c)
+{
+ int n = 1;
+ Verb verb = move;
+
+action:
+ switch(c){
+ /* # of repeats */
+ case '1': case '2': case '3':
+ case '4': case '5': case '6':
+ case '7': case '8': case '9':
+ n = 0;
+ while('0' <= c && c <= '9'){
+ n = 10*n + (c-'0');
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ }
+ goto action;
+
+ /* composable actions */
+ case 'l': verb(term, right(term, n)); break;
+ case 'h': verb(term, left(term, n)); break;
+ case '0': verb(term, HOME(term)); break;
+ case '$': verb(term, END(term)); break;
+ case 'b': verb(term, prevword(term,n)); break;
+ case 'B': verb(term, prevWord(term,n)); break;
+ case 'w': verb(term, nextword(term,n)); break;
+ case 'W': verb(term, nextWord(term,n)); break;
+ case 'e': verb(term, nextend(term,n)); break;
+ case 'E': verb(term, nextEnd(term,n)); break;
+
+ /* verb switches */
+ case 'd': // delete
+ verb = delete;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ /* special cases */
+ switch(c){
+ case 'd':
+ move(term, HOME(term));
+ delete(term, END(term));
+ return 0;
+ default:
+ goto action;
+ }
+ case 'y': // yank
+ verb = yank;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ /* special cases */
+ switch(c){
+ case 'y':
+ if(term->yank.cap < term->edit.len+1){
+ efree(term->yank.buf);
+ term->yank.len = term->edit.len;
+ term->yank.cap = term->edit.len+1;
+ term->yank.buf = emalloc(term->yank.cap);
+ }
+ memcpy(term->yank.buf, term->edit.buf, term->edit.len+1);
+ break;
+ default:
+ goto action;
+ }
+ break;
+
+ case 'p': // put
+ insertbytes(term, term->yank.len, term->yank.buf);
+ refreshline(term);
+ return 0;
+
+ /* special cases
+ * sadly I don't know a better way than to have these checks for move
+ * the vi language doesn't fully compose
+ */
+ case 'i': insertmode:
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ break;
+
+ case 'I':
+ if(verb != move) goto unrecognized;
+ move(term, HOME(term));
+ goto insertmode;
+
+ case 'a':
+ if(verb != move) goto unrecognized;
+ if(term->edit.pos < term->edit.len){
+ term->edit.pos++;
+ refreshline(term);
+ }
+ goto insertmode;
+
+ case 'A':
+ if(verb != move) goto unrecognized;
+ move(term, END(term));
+ goto insertmode;
+
+ case 'x':
+ if(verb != move) goto unrecognized;
+ delete(term, right(term, 1));
+ break;
+
+ case 'X':
+ if(verb != move) goto unrecognized;
+ delete(term, left(term, 1));
+ break;
+
+ case 'r':
+ if(verb != move) goto unrecognized;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ if(c < ' ')
+ break;
+ term->edit.buf[term->edit.pos] = c;
+ refreshline(term);
+ break;
+
+ // TODO: replace mode?
+
+ case 'c':
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ verb = delete;
+ if(read(term->ifd, &c, 1)<1)
+ return -1;
+ goto action;
+
+ case 'C':
+ if(verb != move) goto unrecognized;
+ insertmode(term->ofd);
+ goto deleteln;
+
+ case 'D':
+ if(verb != move) goto unrecognized;
+ deleteln:
+ term->edit.len = term->edit.pos;
+ term->edit.buf[term->edit.pos] = 0;
+ refreshline(term);
+ break;
+
+ default: unrecognized:
+ beep();
+ break;
+ }
+
+ return 0;
+}
+#undef END
+
+#define END(term) (Position){(term).edit.len, (term).cursor.len}
+
+static
+int
+size(char *s)
+{
+ rune c;
+ int n, len = 0;;
+ while((c=*s)){
+ if(c == '\033'){
+ n = 1;
+ esccode:
+ c = s[n];
+ if(!c) // we hit end of string in the middle of parsing an escape code!
+ return len;
+ if(c == 'm'){
+ s += n + 1;
+ continue; // outer loop
+ }
+ n++;
+ goto esccode;
+ }
+ n = utf8·decode(s, &c);
+ s += n;
+ len += utf8·runewidth(c);
+ }
+ return len;
+}
+
+/* this function is the core of the line editing capability of linenoise.
+ * it expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned asap to read().
+ *
+ * the resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * the function returns the length of the current buffer. */
+static
+int
+interact(int ifd, int ofd, char *buf, intptr len, char *prompt)
+{
+ int n, aux;
+ char esc[3];
+ char c[UTFmax+1] = { 0 };
+ rune r;
+
+ struct TerminalState term;
+ /*
+ * populate the state that we pass to functions implementing
+ * specific editing functionalities
+ */
+ term.ifd = ifd;
+ term.ofd = ofd;
+
+ term.edit.buf = buf;
+ term.edit.cap = len;
+ term.edit.len = 0;
+ term.edit.pos = 0;
+
+ term.prompt.s = prompt;
+ term.prompt.len = strlen(prompt);
+ term.prompt.size = size(prompt);
+
+ term.cursor.pos = 0;
+ term.cursor.len = 0;
+ term.cursor.cap = columns(ifd, ofd);
+
+ term.maxrows = 0;
+ term.history = 0;
+
+ term.yank.buf = nil;
+ term.yank.cap = term.yank.len = 0;
+
+ /* buffer starts empty. */
+ term.edit.buf[0] = '\0';
+ term.edit.cap--; /* make sure there is always space for the nulterm */
+
+ /* push current (empty) command onto history stack */
+ addhistory("");
+
+ if(write(term.ofd,prompt,term.prompt.len) == -1)
+ return -1;
+
+ for(;;){
+ n = read(term.ifd,c,1);
+ if(n <= 0)
+ goto finish;
+
+ /* partition input by rune */
+ if(utf8·onebyte(c[0])){
+ r = c[0];
+ }else if(utf8·twobyte(c[0])){
+ n = read(term.ifd,c+1,1);
+ if(n < 1 || (n=utf8·decode(c, &r)) != 2)
+ goto finish;
+ }else if(utf8·threebyte(c[0])){
+ n = read(term.ifd,c+1,2);
+ if(n < 2 || (n=utf8·decode(c, &r)) != 3)
+ goto finish;
+ }else if(utf8·fourbyte(c[0])){
+ n = read(term.ifd,c+1,3);
+ if(n < 3 || (n=utf8·decode(c, &r)) != 4)
+ goto finish;
+ }else
+ goto finish;
+
+ switch(r){
+ case KeyEnter:
+ pophistory();
+ if(mode.multiline)
+ move(&term, END(term));
+ goto finish;
+
+ case KeyCtrlC:
+ errno = EAGAIN;
+ return -1;
+
+ case KeyBackspace:
+ case KeyCtrlH:
+ delete(&term, left(&term, 1));
+ break;
+
+ case KeyCtrlD:
+ if(term.edit.len > 0)
+ delete(&term, right(&term, 1));
+ break;
+
+ case KeyCtrlT:
+ if(term.edit.pos > 0 && term.edit.pos < term.edit.len){
+ aux = buf[term.edit.pos-1];
+
+ buf[term.edit.pos-1] = buf[term.edit.pos];
+ buf[term.edit.pos] = aux;
+
+ if(term.edit.pos != term.edit.len-1)
+ term.edit.pos++;
+
+ refreshline(&term);
+ }
+ break;
+
+ case KeyCtrlB:
+ move(&term, left(&term, 1));
+ break;
+
+ case KeyCtrlF: /* ctrl-f */
+ move(&term, right(&term, 1));
+ break;
+
+ case KeyCtrlP: /* ctrl-p */
+ usehistory(&term, +1);
+ break;
+
+ case KeyCtrlN: /* ctrl-n */
+ usehistory(&term, -1);
+ break;
+
+ case KeyEsc: /* escape sequence */
+ /*
+ * try to read two bytes representing the escape sequence.
+ * if we read less than 2 and we are in vi mode, interpret as command
+ *
+ * NOTE: we could do a timed read here
+ */
+ switch(read(term.ifd,esc,2)){
+ case 0:
+ if(mode.vi.on){
+ if(mode.vi.insert){
+ normalmode(term.ofd);
+ if(term.edit.pos > 0){
+ --term.edit.pos;
+ refreshline(&term);
+ }
+ continue;
+ }
+ }
+ case 1:
+ if(mode.vi.on){
+ if(mode.vi.insert){
+ normalmode(term.ofd);
+ if(vi(&term,esc[0]) < 0){
+ term.edit.len = -1;
+ goto finish;
+ }
+ continue;
+ }
+ }
+ default: // 2
+ ;
+ }
+
+ /* ESC [ sequences. */
+ if(esc[0] == '['){
+ if(0 <= esc[1] && esc[1] <= '9'){
+ /* extended escape, read additional byte. */
+ if(read(term.ifd,esc+2,1) == -1)
+ break;
+
+ if(esc[2] == '~'){
+ switch(esc[1]){
+ case '3': /* delete key. */
+ delete(&term, left(&term,1));
+ break;
+ }
+ }
+ }else{
+ switch(esc[1]) {
+ case 'A': /* up */
+ usehistory(&term, +1);
+ break;
+ case 'B': /* down */
+ usehistory(&term, -1);
+ break;
+ case 'C': /* right */
+ move(&term, right(&term, 1));
+ break;
+ case 'D': /* left */
+ move(&term, left(&term, 1));
+ break;
+ case 'H': /* home */
+ move(&term, HOME(term));
+ break;
+ case 'F': /* end*/
+ move(&term, END(term));
+ break;
+ }
+ }
+ }
+ /* ESC O sequences. */
+ else if(esc[0] == 'O'){
+ switch(esc[1]) {
+ case 'H': /* home */
+ move(&term, HOME(term));
+ break;
+ case 'F': /* end*/
+ move(&term, END(term));
+ break;
+ }
+ }
+ break;
+
+ default:
+ if(mode.vi.on && !mode.vi.insert && n == 1){
+ if(vi(&term,c[0]) < 0){
+ term.edit.len = -1;
+ goto finish;
+ }
+ }else if(!insertrune(&term,n,c)){
+ term.edit.len = -1;
+ goto finish;
+ }
+
+ break;
+
+ case KeyCtrlU: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ term.edit.pos = term.edit.len = 0;
+ term.cursor.pos = term.cursor.len = 0;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlK: /* Ctrl+k, delete from current to end of line. */
+ buf[term.edit.pos] = '\0';
+ term.edit.len = term.edit.pos;
+ term.cursor.len = term.cursor.pos;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlA: /* Ctrl+a, go to the start of the line */
+ move(&term, HOME(term));
+ break;
+
+ case KeyCtrlE: /* ctrl+e, go to the end of the line */
+ move(&term, END(term));
+ break;
+
+ case KeyCtrlL: /* ctrl+term, clear screen */
+ clear();
+ refreshline(&term);
+ break;
+
+ case KeyCtrlW: /* ctrl+w, delete previous word */
+ delete(&term, prevword(&term,1));
+ break;
+ }
+ }
+finish:
+ efree(term.yank.buf);
+ return term.edit.len;
+}
+
+/*
+ * this special mode is used by linenoise in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the linenoise_example program using the --keycodes option.
+ */
+void
+printkeycode(void)
+{
+ int n;
+ char c, quit[4];
+
+ printf("entering debugging mode. printing key codes.\n"
+ "press keys to see scan codes. type 'quit' at any time to exit.\n");
+
+ if(!enterraw(0))
+ return;
+
+ memset(quit,' ',4);
+
+ for(;;){
+ n = read(0,&c,1);
+ if(n <= 0)
+ continue;
+ memmove(quit,quit+1,sizeof(quit)-1); // shift string to left
+ quit[arrlen(quit)-1] = c; /* Insert current char on the right. */
+
+ if(memcmp(quit,"quit",sizeof(quit)) == 0)
+ break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, (int)c);
+ printf("\r"); /* go to left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ exitraw(0);
+}
+
+/*
+ * this function calls the line editing function edit() using the stdin set in raw mode
+ */
+static
+int
+raw(char *buf, intptr len, char *prompt)
+{
+ int n;
+
+ if(!len){
+ errno = EINVAL;
+ return -1;
+ }
+
+ // XXX: should we not hardcode stdin and stdout fd?
+ if(!enterraw(0)) return -1;
+ n = interact(0, 1, buf, len, prompt);
+ exitraw(0);
+
+ return n;
+}
+
+/*
+ * called when readline() is called with the standard
+ * input file descriptor not attached to a TTY. For example when the
+ * program is called in pipe or with a file redirected to its standard input
+ * in this case, we want to be able to return the line regardless of its length
+ */
+static
+int
+notty(void)
+{
+ int c;
+
+ for(;;){
+ c = fgetc(stdin);
+ put(&runner->cmd.io, c);
+ }
+}
+
+void
+enablevi(void)
+{
+ mode.vi.on = 1;
+ insertmode(1);
+}
+
+/*
+ * The high level function that is the main API.
+ * This function checks if the terminal has basic capabilities and later
+ * either calls the line editing function or uses dummy fgets() so that
+ * you will be able to type something even in the most desperate of the
+ * conditions.
+ */
+int
+readline(char *prompt)
+{
+ int n;
+
+ // reset the command buffer
+ runner->cmd.io->e = runner->cmd.io->b = runner->cmd.io->buf;
+
+ if(!shell.interactive)
+ return notty();
+
+ if((n = raw(runner->cmd.io->e, runner->cmd.io->cap-1, prompt)) == -1)
+ return 0;
+ runner->cmd.io->e += n;
+
+ /* insert a newline character at the end */
+ put(&runner->cmd.io, '\n');
+
+ return 1;
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static
+void
+doatexit(void)
+{
+ exitraw(0);
+ normalcursor(1);
+}