From 89e6da0a7cc2cd8551ae31ac623232a0ccce905d Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Thu, 21 Oct 2021 10:27:11 -0700 Subject: feat(rc): vi mode fleshed out --- sys/cmd/rc/input.c | 572 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 379 insertions(+), 193 deletions(-) (limited to 'sys/cmd/rc/input.c') diff --git a/sys/cmd/rc/input.c b/sys/cmd/rc/input.c index 5e5fc78..a89dbda 100644 --- a/sys/cmd/rc/input.c +++ b/sys/cmd/rc/input.c @@ -2,6 +2,35 @@ #include #include +/* don't change order of these without modifying matrix */ +enum +{ + NonPrintable, + Alnum, + Punctation, + 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; @@ -17,24 +46,33 @@ static struct Mode mode; static struct termios originalterm; /* - * The structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities + * the structure represents the state during line editing. + * we pass this state to functions implementing specific editing functionalities */ -struct TerminalState +struct TerminalState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - uintptr buflen; /* Edited line buffer size. */ - char *prompt; /* Prompt to display. */ - uintptr plen; /* Prompt length. */ - uintptr pos; /* Current cursor position. */ - uintptr oldpos; /* Previous refresh cursor position. */ - uintptr len; /* Current edited line length. */ - uintptr cols; /* Number of columns in terminal. */ - uintptr maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ + int ifd; /* terminal stdin file descriptor. */ + int ofd; /* terminal stdout file descriptor. */ + + char *buf; /* edited line buffer. */ + intptr buflen; /* edited line buffer size. */ + + struct { + int cap, len; + char *buf; + } yank; /* yank buffer */ + + char *prompt; /* prompt to display. */ + intptr plen; /* prompt length. */ + + intptr pos; /* current cursor position. */ + intptr oldpos; /* previous refresh cursor position. */ + + intptr len; /* current edited line length. */ + intptr cols; /* number of columns in terminal. */ + + intptr maxrows; /* maximum num of rows used so far (multiline mode) */ + int history_index; /* the history index we are currently editing. */ }; enum @@ -60,10 +98,12 @@ enum KeyBackspace = 127 /* Backspace */ }; - - static void doatexit(void); +/* vi operations */ +typedef intptr (*Noun)(struct TerminalState*, int); +typedef void (*Verb)(struct TerminalState*, intptr); + static void normalcursor(int fd) @@ -269,11 +309,11 @@ refreshsingleline(struct TerminalState *term) char esc[64]; struct Buffer ab; - uintptr plen = term->plen; + intptr plen = term->plen; int fd = term->ofd; char *buf = term->buf; - uintptr len = term->len; - uintptr pos = term->pos; + intptr len = term->len; + intptr pos = term->pos; while((plen+pos) >= term->cols) { buf++; @@ -292,15 +332,7 @@ refreshsingleline(struct TerminalState *term) /* write the prompt and the current buffer content */ append(&ab,term->prompt,strlen(term->prompt)); -#if 0 - if(mode.vi.on){ - if(mode.vi.insert) - append(&ab,"[I]",3); - else - append(&ab,"[N]",3); - } - append(&ab,">",1); -#endif + if(mode.mask == 1) while(len--) append(&ab,"*",1); @@ -419,7 +451,7 @@ refreshline(struct TerminalState *term) /* insert the character 'c' at cursor current position. * on error writing to the terminal -1 is returned, otherwise 0. */ int -insert(struct TerminalState *term, char c) +insertchar(struct TerminalState *term, char c) { char d; if(term->len < term->buflen){ @@ -432,7 +464,7 @@ insert(struct TerminalState *term, char c) d = (mode.mask==1) ? '*' : c; if(write(term->ofd,&d,1) == -1) return 0; - } else + }else refreshline(term); }else{ memmove(term->buf+term->pos+1,term->buf+term->pos,term->len-term->pos); @@ -446,113 +478,160 @@ insert(struct TerminalState *term, char c) return 1; } -/* move cursor to the left n boxes */ -static -void -moveleft(struct TerminalState *term, int n) +int +insertchars(struct TerminalState *term, int len, char *buf) { - if(term->pos > n){ - term->pos -= n; + if(term->len + len > term->buflen) + len = term->buflen - term->len; + + if(term->len == term->pos){ + memcpy(term->buf, buf, len); + term->pos += len; + term->len += len; + term->buf[term->len] = '\0'; + // transfer the modeline here? refreshline(term); - }else if(term->pos){ - term->pos = 0; + }else{ + memmove(term->buf+term->pos+len,term->buf+term->pos,term->len-term->pos); + memcpy(term->buf+term->pos, buf, len); + term->pos += len; + term->len += len; + term->buf[term->len] = '\0'; refreshline(term); } + + return 1; } -/* move cursor to the right n boxes */ +// ----------------------------------------------------------------------- +// vi functionality + +/* modes */ + static void -moveright(struct TerminalState *term, int n) +normalmode(int fd) { - if(term->pos < term->len-n){ - term->pos += n; - refreshline(term); - }else if(term->pos != term->len){ - term->pos = term->len; - refreshline(term); - } + mode.vi.insert = 0; + normalcursor(fd); } -/* Move cursor to the start of the line. */ static void -movehome(struct TerminalState *term) { - if(term->pos != 0){ - term->pos = 0; - refreshline(term); - } +insertmode(int fd) +{ + mode.vi.insert = 1; + insertcursor(fd); } -/* move cursor to the end of the line. */ +/* actions */ + static void -moveend(struct TerminalState *term) +move(struct TerminalState *term, intptr to) { - if(term->pos != term->len){ - term->pos = term->len; + if(to != term->pos){ + term->pos = to; refreshline(term); } } -/* Substitute the currently edited line with the next or previous history - * entry as specified by 'dir'. */ +static void -movehistory(struct TerminalState *term, int dir) +yank(struct TerminalState *term, intptr to) { + intptr len, off; + + if(to == term->pos) + return; // noop + + if(to > term->pos){ + len = to - term->pos; + off = term->pos; + }else{ + len = term->pos - to; + off = to; + } + + 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->buf+off, len); + term->yank.buf[len] = 0; } -/* delete the character at the right of the cursor without altering the cursor - * position. basically this is what happens with the "Delete" keyboard key. */ +static void -delete(struct TerminalState *term) +delete(struct TerminalState *term, intptr to) { - if(term->len > 0 && term->pos < term->len){ - memmove(term->buf+term->pos,term->buf+term->pos+1,term->len-term->pos-1); - term->len--; - term->buf[term->len] = '\0'; - refreshline(term); + intptr diff; + + // delete characters in front of us (exclusive) + if(to > term->pos){ + diff = to - term->pos; + memmove(term->buf+term->pos, term->buf+to, term->len-to+1); + term->len -= diff; + goto refresh; } + + // delete characters behind us + if(to < term->pos){ + diff = term->pos - to; + memmove(term->buf+to, term->buf+term->pos, term->len-term->pos+1); + term->pos = to; + term->len -= diff; + goto refresh; + } + // do nothing + return; + +refresh: + refreshline(term); } -/* backspace implementation. */ +/* movements */ + +// move cursor to the left n boxes static -void -backspace(struct TerminalState *term) +intptr +left(struct TerminalState *term, int n) { - if(term->pos > 0 && term->len > 0){ - memmove(term->buf+term->pos-1,term->buf+term->pos,term->len-term->pos); - term->pos--; - term->len--; - term->buf[term->len] = '\0'; - refreshline(term); - } + intptr d; + d = term->pos - n; + return MAX(d, 0); } -#define ITERATE_BACK_UNTIL(CONDITION) \ - uintptr d, x = term->pos; \ - char *it = term->buf + x; \ - \ - while(n-- > 0 && it > term->buf){ \ - while(it > term->buf && CONDITION(it[-1])) \ - --it; \ - } \ - \ - return it; \ +// move cursor to the right n boxes +static +intptr +right(struct TerminalState *term, int n) +{ + intptr d; + d = term->pos + n; + return MIN(d, term->len); +} + +void +movehistory(struct TerminalState *term, int dir) +{ +} static -int +intptr prevword(struct TerminalState *term, int n) { + int c; char *it = term->buf + term->pos; while(n-- > 0 && it > term->buf){ - /* consume any leading space chars */ - while(isspace(it[-1]) && --it >= term->buf) - ; + while(it > term->buf && ascii[it[-1]] == Space) + --it; - /* consume word chars */ - while(it > term->buf && (isalnum(it[-1]) || it[-1] == '_')) + c = ascii[it[-1]]; + while(it > term->buf && ascii[it[-1]] == c) --it; } @@ -560,18 +639,36 @@ prevword(struct TerminalState *term, int n) } static -int +intptr +prevWord(struct TerminalState *term, int n) +{ + char *it = term->buf + term->pos; + + while(n-- > 0 && it > term->buf){ + while(it > term->buf && ascii[it[-1]] == Space) + --it; + + while(it > term->buf && ascii[it[-1]] != Space) + --it; + } + + return it-term->buf; +} + +static +intptr nextword(struct TerminalState *term, int n) { + int c; char *it = term->buf + term->pos; char *end = term->buf + term->len; while(n-- > 0 && it < end){ - /* consume any leading word chars */ - while(it < end && (isalnum(*it) || *it == '_')) + c = ascii[*it]; + while(it < end && ascii[*it] == c) ++it; - /* consume any space chars */ - while(isspace(*it) && it < end) + + while(it < end && ascii[*it] == Space) ++it; } @@ -579,39 +676,59 @@ nextword(struct TerminalState *term, int n) } static -void -deleteprevword(struct TerminalState *term) +intptr +nextWord(struct TerminalState *term, int n) { - uintptr old_pos = term->pos; - uintptr diff; - - while(term->pos > 0 && term->buf[term->pos-1] == ' ') - term->pos--; - while(term->pos > 0 && term->buf[term->pos-1] != ' ') - term->pos--; + char *it = term->buf + term->pos; + char *end = term->buf + term->len; - diff = old_pos - term->pos; - memmove(term->buf+term->pos,term->buf+old_pos,term->len-old_pos+1); + while(n-- > 0 && it < end){ + while(it < end && ascii[*it] != Space) + ++it; - term->len -= diff; + while(it < end && ascii[*it] == Space) + ++it; + } - refreshline(term); + return it-term->buf; } static -void -normalmode(int fd) +intptr +nextend(struct TerminalState *term, int n) { - mode.vi.insert = 0; - normalcursor(fd); + int c; + char *it = term->buf + term->pos; + char *end = term->buf + term->len; + + while(n-- > 0 && it+1 < end){ + while(it+1 < end && ascii[it[1]] == Space) + ++it; + + c = ascii[it[1]]; + while(it+1 < end && ascii[it[1]] == c) + ++it; + } + + return it-term->buf; } static -void -insertmode(int fd) +intptr +nextEnd(struct TerminalState *term, int n) { - mode.vi.insert = 1; - insertcursor(fd); + char *it = term->buf + term->pos; + char *end = term->buf + term->len; + + while(n-- > 0 && it+1 < end){ + while(it+1 < end && ascii[it[1]] == Space) + ++it; + + while(it < end && ascii[it[1]] != Space) + ++it; + } + + return it-term->buf; } static @@ -619,6 +736,7 @@ int vi(struct TerminalState *term, char c) { int n = 1; + Verb verb = move; action: switch(c){ @@ -634,34 +752,73 @@ action: } goto action; - /* movements */ - case 'l': - moveright(term, n); - break; - - case 'h': - moveleft(term, n); - break; - - case '0': - movehome(term); - break; - - case '$': - moveend(term); + /* composable actions */ + case 'l': verb(term, right(term, n)); break; + case 'h': verb(term, left(term, n)); break; + case '0': verb(term, 0); break; + case '$': verb(term, term->len); 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, 0); + delete(term, term->len); + 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->len+1){ + efree(term->yank.buf); + term->yank.len = term->len; + term->yank.cap = term->len+1; + term->yank.buf = emalloc(term->yank.cap); + } + memcpy(term->yank.buf, term->buf, term->len+1); + break; + default: + goto action; + } break; - case 'b': - term->pos = prevword(term,n); + case 'p': // put + insertchars(term, term->yank.len, term->yank.buf); refreshline(term); - break; + return 0; - case 'w': - term->pos = nextword(term,n); - refreshline(term); + /* 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, 0); + goto insertmode; + case 'a': + if(verb != move) goto unrecognized; if(term->pos < term->len){ term->pos++; refreshline(term); @@ -669,37 +826,54 @@ action: goto insertmode; case 'A': - moveend(term); + if(verb != move) goto unrecognized; + move(term, term->len); goto insertmode; - case 'I': - movehome(term); - goto insertmode; + case 'x': + if(verb != move) goto unrecognized; + delete(term, right(term, 1)); + break; - case 'i': insertmode: - insertmode(term->ofd); + 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->buf[term->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); - /* fallthough */ + goto deleteln; + case 'D': + if(verb != move) goto unrecognized; + deleteln: term->len = term->pos; term->buf[term->pos] = 0; refreshline(term); break; - case 'd': - if(read(term->ifd,&c,1)) - break; - switch(c){ - default: - beep(); - normalmode(term->ofd); - break; - } - - default: + default: unrecognized: beep(); break; } @@ -708,17 +882,17 @@ action: } -/* 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(). +/* 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 + * 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. */ + * the function returns the length of the current buffer. */ static int -interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) +interact(int ifd, int ofd, char *buf, intptr len, char *prompt) { char c; int n; @@ -741,9 +915,12 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) term.maxrows = 0; term.history_index = 0; - /* Buffer starts empty. */ + term.yank.buf = nil; + term.yank.cap = term.yank.len = 0; + + /* buffer starts empty. */ term.buf[0] = '\0'; - term.buflen--; /* Make sure there is always space for the nulterm */ + term.buflen--; /* make sure there is always space for the nulterm */ if(write(term.ofd,prompt,term.plen) == -1) return -1; @@ -751,13 +928,13 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) for(;;){ n = read(term.ifd,&c,1); if(n <= 0) - return term.len; + goto finish; switch(c){ case KeyEnter: if(mode.multiline) - moveend(&term); - return (int)term.len; + move(&term, term.len); + goto finish; case KeyCtrlC: errno = EAGAIN; @@ -765,12 +942,12 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) case KeyBackspace: case KeyCtrlH: - backspace(&term); + delete(&term, left(&term, 1)); break; case KeyCtrlD: if(term.len > 0) - delete(&term); + delete(&term, right(&term, 1)); break; case KeyCtrlT: @@ -784,11 +961,11 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) break; case KeyCtrlB: - moveleft(&term, 1); + move(&term, left(&term, 1)); break; case KeyCtrlF: /* ctrl-f */ - moveright(&term, 1); + move(&term, right(&term, 1)); break; case KeyCtrlP: /* ctrl-p */ @@ -803,6 +980,7 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) /* * 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)){ @@ -821,8 +999,10 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) if(mode.vi.on){ if(mode.vi.insert){ normalmode(term.ofd); - if(vi(&term,esc[0]) < 0) - return -1; + if(vi(&term,esc[0]) < 0){ + term.len = -1; + goto finish; + } continue; } } @@ -840,7 +1020,7 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) if(esc[2] == '~'){ switch(esc[1]){ case '3': /* delete key. */ - delete(&term); + delete(&term, left(&term,1)); break; } } @@ -853,16 +1033,16 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) movehistory(&term, 0); break; case 'C': /* right */ - moveright(&term, 1); + move(&term, right(&term, 1)); break; case 'D': /* left */ - moveleft(&term, 1); + move(&term, left(&term, 1)); break; case 'H': /* home */ - movehome(&term); + move(&term, 0); break; case 'F': /* end*/ - moveend(&term); + move(&term, term.len); break; } } @@ -871,10 +1051,10 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) else if(esc[0] == 'O'){ switch(esc[1]) { case 'H': /* home */ - movehome(&term); + move(&term, 0); break; case 'F': /* end*/ - moveend(&term); + move(&term, term.len); break; } } @@ -883,10 +1063,14 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) default: if(mode.vi.on && !mode.vi.insert){ - if(vi(&term,c) < 0) - return -1; - }else if(!insert(&term,c)) - return -1; + if(vi(&term,c) < 0){ + term.len = -1; + goto finish; + } + }else if(!insertchar(&term,c)){ + term.len = -1; + goto finish; + } break; case KeyCtrlU: /* Ctrl+u, delete the whole line. */ @@ -902,11 +1086,11 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) break; case KeyCtrlA: /* Ctrl+a, go to the start of the line */ - movehome(&term); + move(&term, 0); break; case KeyCtrlE: /* ctrl+e, go to the end of the line */ - moveend(&term); + move(&term, term.len); break; case KeyCtrlL: /* ctrl+term, clear screen */ @@ -915,10 +1099,12 @@ interact(int ifd, int ofd, char *buf, uintptr len, char *prompt) break; case KeyCtrlW: /* ctrl+w, delete previous word */ - deleteprevword(&term); + delete(&term, prevword(&term,1)); break; } } +finish: + efree(term.yank.buf); return term.len; } @@ -964,7 +1150,7 @@ printkeycode(void) */ static int -raw(char *buf, uintptr len, char *prompt) +raw(char *buf, intptr len, char *prompt) { int n; -- cgit v1.2.1