#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); }