aboutsummaryrefslogtreecommitdiff
path: root/sys/cmd/rc/input.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/cmd/rc/input.c')
-rw-r--r--sys/cmd/rc/input.c1045
1 files changed, 1045 insertions, 0 deletions
diff --git a/sys/cmd/rc/input.c b/sys/cmd/rc/input.c
new file mode 100644
index 0000000..cf05382
--- /dev/null
+++ b/sys/cmd/rc/input.c
@@ -0,0 +1,1045 @@
+#include "rc.h"
+#include <termios.h>
+#include <sys/ioctl.h>
+
+struct Mode {
+ ushort raw : 1;
+ ushort multiline : 1;
+ ushort mask : 1;
+ ushort defer : 1;
+ struct {
+ ushort on : 1;
+ ushort insert : 1;
+ } vi ;
+};
+
+static struct Mode mode;
+static struct termios originalterm;
+
+/*
+ * 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. */
+ char *buf; /* Edited line buffer. */
+ uintptr buflen; /* Edited line buffer size. */
+ char *prompt; /* Prompt to display. */
+ uintptr plen; /* Prompt length. */
+ uintptr pos; /* Current cursor position. */
+ uintptr oldpos; /* Previous refresh cursor position. */
+ uintptr len; /* Current edited line length. */
+ uintptr cols; /* Number of columns in terminal. */
+ uintptr maxrows; /* Maximum num of rows used so far (multiline mode) */
+ int history_index; /* The history index we are currently editing. */
+};
+
+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);
+
+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(!isatty(0))
+ 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);
+}
+
+/* =========================== 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;
+
+ uintptr plen = term->plen;
+ int fd = term->ofd;
+ char *buf = term->buf;
+ uintptr len = term->len;
+ uintptr pos = term->pos;
+
+ while((plen+pos) >= term->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while(plen+len > term->cols)
+ len--;
+
+ // TODO: do we need so much malloc pressure?
+ initbuffer(&ab);
+
+ /* 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,strlen(term->prompt));
+#if 0
+ if(mode.vi.on){
+ if(mode.vi.insert)
+ append(&ab,"[I]",3);
+ else
+ append(&ab,"[N]",3);
+ }
+ append(&ab,">",1);
+#endif
+ 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)(pos+plen)); // 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)
+{
+ 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);
+}
+
+/* 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 character 'c' at cursor current position.
+ * on error writing to the terminal -1 is returned, otherwise 0. */
+int
+insert(struct TerminalState *term, char c)
+{
+ char d;
+ if(term->len < term->buflen){
+ if(term->len == term->pos){
+ term->buf[term->pos] = c;
+ term->pos++;
+ term->len++;
+ term->buf[term->len] = '\0';
+ if((!mode.multiline && term->plen+term->len < term->cols)){
+ d = (mode.mask==1) ? '*' : c;
+ if(write(term->ofd,&d,1) == -1)
+ return 0;
+ } else
+ refreshline(term);
+ }else{
+ memmove(term->buf+term->pos+1,term->buf+term->pos,term->len-term->pos);
+ term->buf[term->pos] = c;
+ term->len++;
+ term->pos++;
+ term->buf[term->len] = '\0';
+ refreshline(term);
+ }
+ }
+ return 1;
+}
+
+/* move cursor to the left n boxes */
+static
+void
+moveleft(struct TerminalState *term, int n)
+{
+ if(term->pos > n){
+ term->pos -= n;
+ refreshline(term);
+ }else if(term->pos){
+ term->pos = 0;
+ refreshline(term);
+ }
+}
+
+/* move cursor to the right n boxes */
+static
+void
+moveright(struct TerminalState *term, int n)
+{
+ if(term->pos < term->len-n){
+ term->pos += n;
+ refreshline(term);
+ }else if(term->pos != term->len){
+ term->pos = term->len;
+ refreshline(term);
+ }
+}
+
+/* Move cursor to the start of the line. */
+static
+void
+movehome(struct TerminalState *term) {
+ if(term->pos != 0){
+ term->pos = 0;
+ refreshline(term);
+ }
+}
+
+/* move cursor to the end of the line. */
+static
+void
+moveend(struct TerminalState *term)
+{
+ if(term->pos != term->len){
+ term->pos = term->len;
+ refreshline(term);
+ }
+}
+
+/* Substitute the currently edited line with the next or previous history
+ * entry as specified by 'dir'. */
+void
+movehistory(struct TerminalState *term, int dir)
+{
+}
+
+/* delete the character at the right of the cursor without altering the cursor
+ * position. basically this is what happens with the "Delete" keyboard key. */
+void
+delete(struct TerminalState *term)
+{
+ if(term->len > 0 && term->pos < term->len){
+ memmove(term->buf+term->pos,term->buf+term->pos+1,term->len-term->pos-1);
+ term->len--;
+ term->buf[term->len] = '\0';
+ refreshline(term);
+ }
+}
+
+/* backspace implementation. */
+static
+void
+backspace(struct TerminalState *term)
+{
+ if(term->pos > 0 && term->len > 0){
+ memmove(term->buf+term->pos-1,term->buf+term->pos,term->len-term->pos);
+ term->pos--;
+ term->len--;
+ term->buf[term->len] = '\0';
+ refreshline(term);
+ }
+}
+
+#define ITERATE_BACK_UNTIL(CONDITION) \
+ uintptr d, x = term->pos; \
+ char *it = term->buf + x; \
+ \
+ while(n-- > 0 && it > term->buf){ \
+ while(it > term->buf && CONDITION(it[-1])) \
+ --it; \
+ } \
+ \
+ return it; \
+
+static
+int
+prevword(struct TerminalState *term, int n)
+{
+ char *it = term->buf + term->pos;
+
+ while(n-- > 0 && it > term->buf){
+ /* consume any leading space chars */
+ while(isspace(it[-1]) && --it >= term->buf)
+ ;
+
+ /* consume word chars */
+ while(it > term->buf && (isalnum(it[-1]) || it[-1] == '_'))
+ --it;
+ }
+
+ return it-term->buf;
+}
+
+static
+int
+nextword(struct TerminalState *term, int n)
+{
+ char *it = term->buf + term->pos;
+ char *end = term->buf + term->len;
+
+ while(n-- > 0 && it < end){
+ /* consume any leading word chars */
+ while(it < end && (isalnum(*it) || *it == '_'))
+ ++it;
+ /* consume any space chars */
+ while(isspace(*it) && it < end)
+ ++it;
+ }
+
+ return it-term->buf;
+}
+
+static
+void
+deleteprevword(struct TerminalState *term)
+{
+ uintptr old_pos = term->pos;
+ uintptr diff;
+
+ while(term->pos > 0 && term->buf[term->pos-1] == ' ')
+ term->pos--;
+ while(term->pos > 0 && term->buf[term->pos-1] != ' ')
+ term->pos--;
+
+ diff = old_pos - term->pos;
+ memmove(term->buf+term->pos,term->buf+old_pos,term->len-old_pos+1);
+
+ term->len -= diff;
+
+ refreshline(term);
+}
+
+static
+void
+normalmode(int fd)
+{
+ mode.vi.insert = 0;
+ normalcursor(fd);
+}
+
+static
+void
+insertmode(int fd)
+{
+ mode.vi.insert = 1;
+ insertcursor(fd);
+}
+
+static
+int
+vi(struct TerminalState *term, char c)
+{
+ int n = 1;
+
+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;
+
+ /* movements */
+ case 'l':
+ moveright(term, n);
+ break;
+
+ case 'h':
+ moveleft(term, n);
+ break;
+
+ case '0':
+ movehome(term);
+ break;
+
+ case '$':
+ moveend(term);
+ break;
+
+ case 'b':
+ term->pos = prevword(term,n);
+ refreshline(term);
+ break;
+
+ case 'w':
+ term->pos = nextword(term,n);
+ refreshline(term);
+ break;
+
+ case 'a':
+ if(term->pos < term->len){
+ term->pos++;
+ refreshline(term);
+ }
+ goto insertmode;
+
+ case 'A':
+ moveend(term);
+ goto insertmode;
+
+ case 'I':
+ movehome(term);
+ goto insertmode;
+
+ case 'i': insertmode:
+ insertmode(term->ofd);
+ break;
+
+ case 'C':
+ insertmode(term->ofd);
+ /* fallthough */
+ case 'D':
+ term->len = term->pos;
+ term->buf[term->pos] = 0;
+ refreshline(term);
+ break;
+
+ case 'd':
+ if(read(term->ifd,&c,1))
+ break;
+ switch(c){
+ default:
+ beep();
+ normalmode(term->ofd);
+ break;
+ }
+
+ default:
+ beep();
+ break;
+ }
+
+ return 0;
+}
+
+
+/* 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, uintptr len, char *prompt)
+{
+ char c;
+ int n;
+ char esc[3];
+
+ struct TerminalState term;
+ /*
+ * populate the state that we pass to functions implementing
+ * specific editing functionalities
+ */
+ term.ifd = ifd;
+ term.ofd = ofd;
+ term.buf = buf;
+ term.buflen = len;
+ term.prompt = prompt;
+ term.plen = strlen(prompt);
+ term.oldpos = term.pos = 0;
+ term.len = 0;
+ term.cols = columns(ifd, ofd);
+ term.maxrows = 0;
+ term.history_index = 0;
+
+ /* Buffer starts empty. */
+ term.buf[0] = '\0';
+ term.buflen--; /* Make sure there is always space for the nulterm */
+
+ if(write(term.ofd,prompt,term.plen) == -1)
+ return -1;
+
+ for(;;){
+ n = read(term.ifd,&c,1);
+ if(n <= 0)
+ return term.len;
+
+ switch(c){
+ case KeyEnter:
+ if(mode.multiline)
+ moveend(&term);
+ return (int)term.len;
+
+ case KeyCtrlC:
+ errno = EAGAIN;
+ return -1;
+
+ case KeyBackspace:
+ case KeyCtrlH:
+ backspace(&term);
+ break;
+
+ case KeyCtrlD:
+ if(term.len > 0)
+ delete(&term);
+ break;
+
+ case KeyCtrlT:
+ if(term.pos > 0 && term.pos < term.len){
+ int aux = buf[term.pos-1];
+ buf[term.pos-1] = buf[term.pos];
+ buf[term.pos] = aux;
+ if (term.pos != term.len-1) term.pos++;
+ refreshline(&term);
+ }
+ break;
+
+ case KeyCtrlB:
+ moveleft(&term, 1);
+ break;
+
+ case KeyCtrlF: /* ctrl-f */
+ moveright(&term, 1);
+ break;
+
+ case KeyCtrlP: /* ctrl-p */
+ /* TODO next history */
+ break;
+
+ case KeyCtrlN: /* ctrl-n */
+ /* TODO prev history */
+ 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.pos > 0){
+ --term.pos;
+ refreshline(&term);
+ }
+ continue;
+ }
+ }
+ case 1:
+ if(mode.vi.on){
+ if(mode.vi.insert){
+ normalmode(term.ofd);
+ if(vi(&term,esc[0]) < 0)
+ return -1;
+ 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);
+ break;
+ }
+ }
+ }else{
+ switch(esc[1]) {
+ case 'A': /* up */
+ movehistory(&term, 1);
+ break;
+ case 'B': /* down */
+ movehistory(&term, 0);
+ break;
+ case 'C': /* right */
+ moveright(&term, 1);
+ break;
+ case 'D': /* left */
+ moveleft(&term, 1);
+ break;
+ case 'H': /* home */
+ movehome(&term);
+ break;
+ case 'F': /* end*/
+ moveend(&term);
+ break;
+ }
+ }
+ }
+ /* ESC O sequences. */
+ else if(esc[0] == 'O'){
+ switch(esc[1]) {
+ case 'H': /* home */
+ movehome(&term);
+ break;
+ case 'F': /* end*/
+ moveend(&term);
+ break;
+ }
+ }
+
+ break;
+
+ default:
+ if(mode.vi.on && !mode.vi.insert){
+ if(vi(&term,c) < 0)
+ return -1;
+ }else if(!insert(&term,c))
+ return -1;
+ break;
+
+ case KeyCtrlU: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ term.pos = term.len = 0;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlK: /* Ctrl+k, delete from current to end of line. */
+ buf[term.pos] = '\0';
+ term.len = term.pos;
+ refreshline(&term);
+ break;
+
+ case KeyCtrlA: /* Ctrl+a, go to the start of the line */
+ movehome(&term);
+ break;
+
+ case KeyCtrlE: /* ctrl+e, go to the end of the line */
+ moveend(&term);
+ break;
+
+ case KeyCtrlL: /* ctrl+term, clear screen */
+ clear();
+ refreshline(&term);
+ break;
+
+ case KeyCtrlW: /* ctrl+w, delete previous word */
+ deleteprevword(&term);
+ break;
+ }
+ }
+ return term.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, uintptr 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(&shell->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
+ shell->cmd.io->e = shell->cmd.io->b = shell->cmd.io->buf;
+
+ if(!isatty(0))
+ return notty();
+
+ if((n = raw(shell->cmd.io->e, shell->cmd.io->cap-1, prompt)) == -1)
+ return 0;
+ shell->cmd.io->e += n;
+
+ /* insert a newline character at the end */
+ put(&shell->cmd.io, '\n');
+ printf("\n");
+
+ return 1;
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static
+void
+doatexit(void)
+{
+ exitraw(0);
+ normalcursor(1);
+}