From ce05175372a9ddca1a225db0765ace1127a39293 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 12 Nov 2021 09:22:01 -0800 Subject: chore: simplified organizational structure --- sys/cmd/rc/input.c | 1679 ---------------------------------------------------- 1 file changed, 1679 deletions(-) delete mode 100644 sys/cmd/rc/input.c (limited to 'sys/cmd/rc/input.c') 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 -#include - -/* 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); -} -- cgit v1.2.1