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.c1679
1 files changed, 0 insertions, 1679 deletions
diff --git a/sys/cmd/rc/input.c b/sys/cmd/rc/input.c
deleted file mode 100644
index cc2383d..0000000
--- a/sys/cmd/rc/input.c
+++ /dev/null
@@ -1,1679 +0,0 @@
-#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);
-}