From 425ef692da7e74112f88f0b368f3286dba84f846 Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Thu, 18 Jun 2020 19:45:40 -0700 Subject: feat: working parser for rc shell language --- sys/libterm/buffer.c | 326 ---------- sys/libterm/escseq.c | 0 sys/libterm/events.c | 1692 -------------------------------------------------- sys/libterm/input.c | 108 ---- sys/libterm/term.c | 256 +++++--- sys/libterm/term.h | 365 +++-------- sys/libterm/window.c | 426 ++++++++++++- 7 files changed, 643 insertions(+), 2530 deletions(-) delete mode 100644 sys/libterm/buffer.c delete mode 100644 sys/libterm/escseq.c delete mode 100644 sys/libterm/events.c delete mode 100644 sys/libterm/input.c (limited to 'sys/libterm') diff --git a/sys/libterm/buffer.c b/sys/libterm/buffer.c deleted file mode 100644 index b903e71..0000000 --- a/sys/libterm/buffer.c +++ /dev/null @@ -1,326 +0,0 @@ -#include "term.h" - -/* row operations */ -void -zero(Row *row, int start, int len) -{ - int i; - Cell cell = { - .r = L'\0', - .pen = {0}, - }; - - for (i = start; i < len + start; i++) - row->cells[i] = cell; - row->dirty = true; -} - -void -roll(Row *start, Row *end, int count) -{ - int n = end - start; - - count %= n; - if (count < 0) - count += n; - - if (count) { - char buf[count * sizeof(Row)]; - memcpy(buf, start, count * sizeof(Row)); - memmove(start, start + count, (n - count) * sizeof(Row)); - memcpy(end - count, buf, count * sizeof(Row)); - for (Row *row = start; row < end; row++) - row->dirty = true; - } -} - -/* buffer operations */ -void -bclear(Buffer *b) -{ - int i; - Cell cell = { - .r = L'\0', - .pen = { - .state = PenNormal, - .col.fg = -1, - .col.bg = -1, - }, - }; - - for (i = 0; i < b->nrow; i++) { - Row *row = b->row + i; - for (int j = 0; j < b->ncol; j++) { - row->cells[j] = cell; - row->dirty = true; - } - } -} - -void -bfree(Buffer *b) -{ - int i; - - for (i = 0; i < b->nrow; i++) - free(b->row[i].cells); - - free(b->row); - - for (i = 0; i < b->scroll.size; i++) - free(b->scroll.buf[i].cells); - - free(b->scroll.buf); - free(b->tabs); -} - -void -bscroll(Buffer *b, int s) -{ - /* work in screenfuls */ - int ssz = b->scroll.bot - b->scroll.top; - - if (s > ssz) { - bscroll(b, ssz); - bscroll(b, s - ssz); - return; - } - if (s < -ssz) { - bscroll(b, -ssz); - bscroll(b, s + ssz); - return; - } - - b->scroll.above += s; - if (b->scroll.above >= b->scroll.size) - b->scroll.above = b->scroll.size; - - if (s > 0 && b->scroll.size) { - for (int i = 0; i < s; i++) { - Row tmp = b->scroll.top[i]; - b->scroll.top[i] = b->scroll.buf[b->scroll.index]; - b->scroll.buf[b->scroll.index] = tmp; - - b->scroll.index++; - if (b->scroll.index == b->scroll.size) - b->scroll.index = 0; - } - } - roll(b->scroll.top, b->scroll.bot, s); - if (s < 0 && b->scroll.size) { - for (int i = (-s) - 1; i >= 0; i--) { - b->scroll.index--; - if (b->scroll.index == -1) - b->scroll.index = b->scroll.size - 1; - - Row tmp = b->scroll.top[i]; - b->scroll.top[i] = b->scroll.buf[b->scroll.index]; - b->scroll.buf[b->scroll.index] = tmp; - b->scroll.top[i].dirty = true; - } - } -} - -void -bresize(Buffer *b, int nrow, int ncol) -{ - Row *row = b->row; - - if (b->nrow != nrow) { - if (b->crow >= row + nrow) { - /* scroll up instead of simply chopping off bottom */ - bscroll(b, (b->crow - b->row) - nrow + 1); - } - while (b->nrow > nrow) { - free(row[b->nrow - 1].cells); - b->nrow--; - } - - row = realloc(row, sizeof(Row) * nrow); - } - - if (b->maxcols < ncol) { - for (int r = 0; r < b->nrow; r++) { - row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol); - if (b->ncol < ncol) - zero(row + r, b->ncol, ncol - b->ncol); - row[r].dirty = true; - } - Row *sbuf = b->scroll.buf; - for (int r = 0; r < b->scroll.size; r++) { - sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol); - if (b->ncol < ncol) - zero(sbuf + r, b->ncol, ncol - b->ncol); - } - b->tabs = realloc(b->tabs, sizeof(*b->tabs) * ncol); - for (int c = b->ncol; c < ncol; c++) - b->tabs[c] = !(c & 7); - b->maxcols = ncol; - b->ncol = ncol; - } else if (b->ncol != ncol) { - for (int r = 0; r < b->nrow; r++) - row[r].dirty = true; - b->ncol = ncol; - } - - int deltarows = 0; - if (b->nrow < nrow) { - while (b->nrow < nrow) { - row[b->nrow].cells = calloc(b->maxcols, sizeof(Cell)); - zero(row + b->nrow, 0, b->maxcols); - b->nrow++; - } - - /* prepare for backfill */ - if (b->crow >= b->scroll.bot - 1) { - deltarows = b->row + nrow - b->crow - 1; - if (deltarows > b->scroll.above) - deltarows = b->scroll.above; - } - } - - b->crow += row - b->row; - b->scroll.top = row; - b->scroll.bot = row + nrow; - b->row = row; - - /* perform backfill */ - if (deltarows > 0) { - bscroll(b, -deltarows); - b->crow += deltarows; - } -} - -bool -binit(Buffer *b, int rows, int cols, int size) -{ - b->pen.state = PenNormal; - b->pen.col.fg = b->pen.col.fg = -1; - - if (size < 0) - size = 0; - if (size && !(b->scroll.buf = calloc(size, sizeof(Row)))) - return false; - - b->scroll.size = size; - bresize(b, rows, cols); - return true; -} - -void -bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) -{ - if (bs) - *bs = nil; - if (be) - *be = nil; - if (as) - *as = nil; - if (ae) - *ae = nil; - if (!b->scroll.size) - return; - - if (b->scroll.above) { - if (bs) - *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size]; - if (be) - *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size]; - } - if (b->scroll.below) { - if (as) - *as = &b->scroll.buf[b->scroll.index]; - if (ae) - *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size]; - } -} - -Row * -browfirst(Buffer *b) -{ - Row *bstart; - if (!b->scroll.size || !b->scroll.above) - return b->row; - bboundary(b, &bstart, nil, nil, nil); - return bstart; -} - -Row * -browlast(Buffer *b) -{ - Row *aend; - if (!b->scroll.size || !b->scroll.below) - return b->row + b->nrow - 1; - bboundary(b, nil, nil, nil, &aend); - return aend; -} - -Row * -brownext(Buffer *b, Row *row) -{ - Row *before_start, *before_end, *after_start, *after_end; - Row *first = b->row, *last = b->row + b->nrow - 1; - - if (!row) - return nil; - - bboundary(b, &before_start, &before_end, &after_start, &after_end); - - if (row >= first && row < last) - return ++row; - if (row == last) - return after_start; - if (row == before_end) - return first; - if (row == after_end) - return nil; - if (row == &b->scroll.buf[b->scroll.size - 1]) - return b->scroll.buf; - return ++row; -} - -Row * -bprevrow(Buffer *b, Row *row) -{ - Row *before_start, *before_end, *after_start, *after_end; - Row *first = b->row, *last = b->row + b->nrow - 1; - - if (!row) - return nil; - - bboundary(b, &before_start, &before_end, &after_start, &after_end); - - if (row > first && row <= last) - return --row; - if (row == first) - return before_end; - if (row == before_start) - return nil; - if (row == after_start) - return last; - if (row == b->scroll.buf) - return &b->scroll.buf[b->scroll.size - 1]; - return --row; -} - -void -brender(Buffer *b, Term *t) -{ - int r, c, n; - char u[UTFmax+1]; - Row *row; - Cell *cell; - - for (r = 0; r < b->nrow; r++) { - row = b->row + r; - if (!row->dirty) - continue; - - for (c = 0; c < b->ncol; c++) { - cell = row->cells + c; - tsetpen(t, cell->pen); - n = utf8·runetobyte(u, &cell->r); - twrite(t, n, u); - } - } -} diff --git a/sys/libterm/escseq.c b/sys/libterm/escseq.c deleted file mode 100644 index e69de29..0000000 diff --git a/sys/libterm/events.c b/sys/libterm/events.c deleted file mode 100644 index 80bc99a..0000000 --- a/sys/libterm/events.c +++ /dev/null @@ -1,1692 +0,0 @@ -#include "term.h" - -#include - -#define bufcount(in) in->buf.c - in->buf.b - -enum { - NodeKey, - NodeArr, -}; - -struct Node -{ - int type; -}; - -struct KeyNode -{ - struct Node; - struct KeyInfo key; -}; - -struct ArrNode -{ - struct Node; - uchar min, max; - Node *arr[]; -}; - -// ----------------------------------------------------------------------- -// loads data into trie - -static enum KeyEvent peekmousekey(Input *in, Key *key, ulong *nb); - -#define FuncNameMax 10 -static struct { - char *name; - int type; - int sym; - int mods; -} funcs[] = -{ - /* THIS LIST MUST REMAIN SORTED ALPHABETICALLY! */ - { "backspace", KeySym, SymBackspace, 0 }, - { "begin", KeySym, SymBegin, 0 }, - { "beg", KeySym, SymBegin, 0 }, - { "btab", KeySym, SymTab, ModShift}, - { "cancel", KeySym, SymCancel, 0 }, - { "clear", KeySym, SymClear, 0 }, - { "close", KeySym, SymClose, 0 }, - { "command", KeySym, SymCommand, 0 }, - { "copy", KeySym, SymCopy, 0 }, - { "dc", KeySym, SymDelete, 0 }, - { "down", KeySym, SymDown, 0 }, - { "end", KeySym, SymEnd, 0 }, - { "enter", KeySym, SymEnter, 0 }, - { "exit", KeySym, SymExit, 0 }, - { "find", KeySym, SymFind, 0 }, - { "help", KeySym, SymHelp, 0 }, - { "home", KeySym, SymHome, 0 }, - { "ic", KeySym, SymInsert, 0 }, - { "left", KeySym, SymLeft, 0 }, - { "mark", KeySym, SymMark, 0 }, - { "message", KeySym, SymMessage, 0 }, - { "move", KeySym, SymMove, 0 }, - { "next", KeySym, SymPagedown, 0 }, // Not quite, but it's the best we can do - { "npage", KeySym, SymPagedown, 0 }, - { "open", KeySym, SymOpen, 0 }, - { "options", KeySym, SymOptions, 0 }, - { "ppage", KeySym, SymPageup, 0 }, - { "previous", KeySym, SymPageup, 0 }, // Not quite, but it's the best we can do - { "print", KeySym, SymPrint, 0 }, - { "redo", KeySym, SymRedo, 0 }, - { "reference", KeySym, SymReference, 0 }, - { "refresh", KeySym, SymRefresh, 0 }, - { "replace", KeySym, SymReplace, 0 }, - { "restart", KeySym, SymRestart, 0 }, - { "resume", KeySym, SymResume, 0 }, - { "right", KeySym, SymRight, 0 }, - { "save", KeySym, SymSave, 0 }, - { "select", KeySym, SymSelect, 0 }, - { "suspend", KeySym, SymSuspend, 0 }, - { "undo", KeySym, SymUndo, 0 }, - { "up", KeySym, SymUp, 0 }, - { nil }, -}; - -// ----------------------------------------------------------------------- -// utility functions - -static -int -stricmp(char *s, char *t) -{ - if (s == nil) - if (t == nil) - return 0; - else - return -strlen(t); - else - if (t == nil) - return +strlen(s); - - int d; - for (; *s && *t; s++, t++) { - d = tolower(*s) - tolower(*t); - if (d < 0 || d > 0) - return d; - } - - /* XXX: not sure about passing in 0 here */ - return tolower(*s) - tolower(*t); -} - -// ----------------------------------------------------------------------- -// bytes -> keysymbols - -static -Node * -keynode(int type, int sym, int mask, int set) -{ - struct KeyNode *n; - n = malloc(sizeof(*n)); - if (!n) - panicf("out of memory"); - - n->type = NodeKey; - n->key.type = type; - n->key.sym = sym; - n->key.modmask = mask; - n->key.modset = set; - - return (Node *)n; -} - -static -Node * -arrnode(uchar min, uchar max) -{ - int nb, i; - struct ArrNode *n; - nb = ((int)max-min+1)*sizeof(*n->arr); - n = malloc(sizeof(*n) + nb); - if (!n) - panicf("out of memory"); - - n->type = NodeArr; - n->min = min, n->max = max; - for (i = 0; i <= max-min; i++) - n->arr[i] = nil; - - return (Node *)n; -} - - -static -Node * -nlookup(Node *n, uchar b) -{ - struct ArrNode *arr; - switch (n->type) { - case NodeKey: - panicf("attempting to subdivide a leaf key node"); - return nil; - case NodeArr: - arr = (struct ArrNode *)n; - if (b < arr->min || b > arr->max) - return nil; - return arr->arr[b - arr->min]; - default: - panicf("unrecognized key node type"); - return nil; - } -} - -static -Node * -compress(Node *root) -{ - int i; - uchar min, max; - struct ArrNode *n, *new; - - if (!root) - return root; - - switch (root->type) { - case NodeKey: - return root; - case NodeArr: - n = (struct ArrNode *)root; - /* find the zeros */ - for (min = 0; !n->arr[min]; min++) { - if (min == 255 && !n->arr[min]) { - free(n); - return arrnode(1, 0); - } - } - - for (max = 0xff; !n->arr[max]; max--) - ; - - new = (struct ArrNode *)arrnode(min, max); - - for (i = min; i <= max; i++) - new->arr[i-min] = compress(n->arr[i]); - - free(n); - return (Node*)new; - default: - panicf("unrecognized key node type"); - return nil; - } - - return root; -} - -static -void -teardown(Node *n) -{ - int i; - struct ArrNode *arr; - - switch (n->type) { - case NodeKey: - break; - case NodeArr: - arr = (struct ArrNode *)n; - for (i = arr->min; i <= arr->max; i++) - if (arr->arr[i - arr->min]) - teardown(arr->arr[i - arr->min]); - break; - default: - return; - } - - free(n); -} - -static -int -insert(Node *root, Node *n, char *seq) -{ - int pos; - uchar b; - Node *nn; - struct ArrNode *a; - - for (pos = 0; (b = seq[pos]); pos++) { - nn = nlookup(root, b); - if (!nn) - break; - root = nn; - a = (struct ArrNode *)root; - } - - for (; (b = seq[pos]); pos++) { - nn = (seq[pos+1]) ? arrnode(0, 0xff) : n; - if (!nn) - return 0; - - if (root->type != NodeArr) - panicf("inserted child node onto leaf 'key' node"); - - a = (struct ArrNode*)root; - if (b < a->min || b > a->max) - panicf("out of bound trie insertion"); - - a->arr[b-a->min] = root = nn; - } - - return 1; -} - -/* unibilium helpers */ -static -enum unibi_string -nametostr(char *name) -{ - enum unibi_string ret; - for (ret = unibi_string_begin_+1; ret < unibi_string_end_; ret++) - if (!strcmp(unibi_name_str(ret), name)) - return ret; - - return -1; -} - -static -char * -unibi_lookup(unibi_term *info, char *name) -{ - enum unibi_string idx; - if ((idx = nametostr(name)) == -1) - return nil; - - return (char*)unibi_get_str(info, idx); -} - -static -Node * -tryloadkey(unibi_term *info, char *name, char **val, struct KeyInfo *key) -{ - char *v; - - v = unibi_lookup(info, name); - if (!v || v[0] == 0) - return nil; - - *val = v; - return keynode(key->type, key->sym, key->modmask, key->modset); -} - -static -Node * -loadtermkeys(unibi_term *info) -{ - int i; - struct Node *root, *n; - char *val, name[5 + FuncNameMax + 1]; - - root = arrnode(0, 0xff); - if (!root) - panicf("out of memory"); - - /* load key syms */ - for (i = 0; funcs[i].name; i++) { - sprintf(name, "key_%s", funcs[i].name); - n = tryloadkey(info, name, &val, &(struct KeyInfo){ - .type = funcs[i].type, - .sym = funcs[i].sym, - .modmask = funcs[i].mods, - .modset = funcs[i].mods, - }); - - /* try shift modified */ - if (!n) - n = tryloadkey(info, name, &val, &(struct KeyInfo){ - .type = funcs[i].type, - .sym = funcs[i].sym, - .modmask = funcs[i].mods | ModShift, - .modset = funcs[i].mods | ModShift, - }); - - /* give up */ - if (n) - insert(root, n, val); - } - - /* load function keys */ - for (i = 1; i < 0xff; i++) { - sprintf(name, "key_f%d", i); - n = tryloadkey(info, name, &val, &(struct KeyInfo){ - .type = KeyFunc, - .sym = i, - .modmask = 0, - .modset = 0, - }); - if (!n) - break; - - insert(root, n, val); - } - - /* load mouse keys */ - val = unibi_lookup(info, "key_mouse"); - if (val && !strcmp(val, "\x1b[M")) { - n = keynode(KeyMouse, 0, 0, 0); - insert(root, n, val); - } - - return compress(root); -} - -static -enum KeyEvent -peeksym(Input *in, Key *key, int force, ulong *nb) -{ - int res; - uchar *b, *o; - Node *root; - struct KeyNode *kn; - - root = in->keys; - b = in->buf.b; - while (b <= in->buf.c) { - printf("checking '%s'\n", b); - root = nlookup(root, *b); - if (!root) - break; - b++; - - if (root->type != NodeKey) - continue; - - kn = (struct KeyNode*)root; - if (kn->key.type == KeyMouse) { - o = in->buf.b, in->buf.b = b; - res = peekmousekey(in, key, nb); - in->buf.b = o; - - if (res == EvKey) - *nb += b - in->buf.b; - - return res; - } - key->type = kn->key.type; - key->mods = kn->key.modset; - key->code.sym = kn->key.type; - *nb = b - in->buf.b; - - return EvKey; - } - - if (root && !force) - return EvAgain; - - return EvNil; -} - -// ----------------------------------------------------------------------- -// misc commands - -static -inline -void -setpos(Key *key, int row, int col) -{ - row = MIN(row, 0xfff); - col = MIN(col, 0xfff); - - key->code.mouse[1] = (row & 0x0ff); - key->code.mouse[2] = (col & 0x0ff); - key->code.mouse[3] = (row & 0xf00) >> 8 | (col & 0x300) >> 4; -} - -static -inline -void -getpos(Key *key, int *row, int *col) -{ - if (col) - *col = ((uchar)key->code.mouse[1] | ((uchar)key->code.mouse[3] & 0x0f) << 8) - 1; - - if (row) - *row = ((uchar)key->code.mouse[2] | ((uchar)key->code.mouse[3] & 0x70) << 4) - 1; -} - -// ----------------------------------------------------------------------- -// csi/ss3 commands - -static int csiinit; - -static struct KeyInfo ss3[64]; -static char ss3kpalts[64]; - -static struct KeyInfo csiss3[64]; -static enum KeyEvent (*do_csi[64])(Input *in, Key *key, int cmd, int narg, long *arg); - -static struct KeyInfo csifuncs[35]; - -/* csi/ss3 cmd keys */ -static -enum KeyEvent -do_csi_full(Input *in, Key *key, int cmd, int narg, long *arg) -{ - if(narg > 1 && arg[1] != -1) - key->mods = arg[1] - 1; - else - key->mods = 0; - - key->type = csiss3[cmd - 0x40].type; - key->code.sym = csiss3[cmd - 0x40].sym; - key->mods &= ~(csiss3[cmd - 0x40].modmask); - key->mods |= csiss3[cmd - 0x40].modset; - - if(key->code.sym == SymUnknown) - return EvNil; - - return EvKey; -} - -static -void -put_csi_full(int type, int sym, int modset, int modmask, uchar cmd) -{ - if(cmd < 0x40 || cmd >= 0x80) - return; - - csiss3[cmd - 0x40].type = type; - csiss3[cmd - 0x40].sym = sym; - csiss3[cmd - 0x40].modset = modset; - csiss3[cmd - 0x40].modmask = modmask; - - do_csi[cmd - 0x40] = &do_csi_full; -} - -/* ss3 kpad keys */ -static -void -put_ss3_kpalt(int type, int sym, uchar cmd, char kpalt) -{ - if(cmd < 0x40 || cmd >= 0x80) - return; - - ss3[cmd - 0x40].type = type; - ss3[cmd - 0x40].sym = sym; - ss3[cmd - 0x40].modset = 0; - ss3[cmd - 0x40].modmask= 0; - ss3kpalts[cmd - 0x40] = kpalt; -} - -/* csi number ~func keys */ -static void emitcodepoint(Input *in, rune r, Key *key); - -static -enum KeyEvent -do_csi_func(Input *in, Key *key, int cmd, int narg, long *arg) -{ - if (narg > 1 && arg[1] != -1) - key->mods = arg[1] - 1; - else - key->mods = 0; - - key->type = KeySym; - - if (arg[0] == 27) { - int mod = key->mods; - emitcodepoint(in, (rune)arg[2], key); - key->mods |= mod; - } else if (arg[0] >= 0 && arg[0] < arrlen(csifuncs)) { - key->type = csifuncs[arg[0]].type; - key->code.sym = csifuncs[arg[0]].sym; - key->mods &= ~(csifuncs[arg[0]].modmask); - key->mods |= csifuncs[arg[0]].modset; - } else - key->code.sym = SymUnknown; - - if (key->code.sym == SymUnknown) - return EvNil; - - return EvKey; -} - -static -void -put_csi_func(int type, int sym, int num) -{ - if(num >= arrlen(csifuncs)) - return; - - csifuncs[num].type = type; - csifuncs[num].sym = sym; - csifuncs[num].modset = 0; - csifuncs[num].modmask = 0; - - do_csi['~' - 0x40] = &do_csi_func; -} - -/* CSI u extended unicode keys */ -static -enum KeyEvent -do_csi_u(Input *in, Key *key, int cmd, int narg, long *arg) -{ - switch(cmd) { - case 'u': { - if(narg > 1 && arg[1] != -1) - key->mods = arg[1] - 1; - else - key->mods = 0; - - int mod = key->mods; - key->type = KeySym; - emitcodepoint(in, arg[0], key); - key->mods |= mod; - - return EvKey; - } - default: - return EvNil; - } -} - -/* csi m/M mouse events */ - -static -enum KeyEvent -do_csi_m(Input *in, Key *key, int cmd, int narg, long *arg) -{ - int initial = cmd >> 8; - cmd &= 0xff; - - switch(cmd) { - case 'M': - case 'm': - break; - default: - return EvNil; - } - - // rxvt protocol - if(!initial && narg >= 3) { - key->type = KeyMouse; - key->code.mouse[0] = arg[0]; - - key->mods = (key->code.mouse[0] & 0x1c) >> 2; - key->code.mouse[0] &= ~0x1c; - - setpos(key, arg[1], arg[2]); - - return EvKey; - } - - // SGR protocol - if(initial == '<' && narg >= 3) { - key->type = KeyMouse; - key->code.mouse[0] = arg[0]; - - key->mods = (key->code.mouse[0] & 0x1c) >> 2; - key->code.mouse[0] &= ~0x1c; - - setpos(key, arg[1], arg[2]); - - if(cmd == 'm') // release - key->code.mouse[3] |= 0x80; - - return EvKey; - } - - return EvNil; -} - -/* csi ? R position events */ - -static -enum KeyEvent -do_csi_R(Input *in, Key *key, int cmd, int narg, long *arg) -{ - switch(cmd) { - case 'R'|'?'<<8: - if(narg < 2) - return EvNil; - key->type = KeyPosition; - setpos(key, arg[1], arg[0]); - return EvKey; - default: - return do_csi_full(in, key, cmd, narg, arg); - } -} - -/* csi $y mode status events */ - -static -enum KeyEvent -do_csi_y(Input *in, Key *key, int cmd, int narg, long *arg) -{ - switch (cmd) { - case 'y'|'$'<<16: - case 'y'|'$'<<16 | '?'<<8: - if (narg < 2) - return EvNil; - - key->type = KeyModeReport; - key->code.mouse[0] = (cmd >> 8); - key->code.mouse[1] = arg[0] >> 8; - key->code.mouse[2] = arg[0] & 0xff; - key->code.mouse[3] = arg[1]; - return EvKey; - - default: - return EvNil; - } -} - -/* parse csi events */ -static -enum KeyEvent -parse_csi(Input *in, ulong introlen, ulong *csi_len, ulong *nargs, long args[], unsigned long *commandp) -{ - ulong csi_end = introlen; - - while (csi_end < bufcount(in)) { - if (in->buf.b[csi_end] >= 0x40 && in->buf.b[csi_end] < 0x80) - break; - csi_end++; - } - - if(csi_end >= bufcount(in)) - return EvAgain; - - uchar cmd = in->buf.b[csi_end]; - *commandp = cmd; - - char present = 0; - int argi = 0; - - ulong p = introlen; - - // See if there is an initial byte - if (in->buf.b[p] >= '<' && in->buf.b[p] <= '?') { - *commandp |= (in->buf.b[p] << 8); - p++; - } - - // Now attempt to parse out up number;number;... separated values - while (p < csi_end) { - uchar c = in->buf.b[p]; - - if (c >= '0' && c <= '9') { - if (!present) { - args[argi] = c - '0'; - present = 1; - } else { - args[argi] = (args[argi] * 10) + c - '0'; - } - } else if(c == ';') { - if (!present) - args[argi] = -1; - present = 0; - argi++; - - if(argi > 16) - break; - } else if (c >= 0x20 && c <= 0x2f) { - *commandp |= c << 16; - break; - } - p++; - } - - if(present) - argi++; - - *nargs = argi; - *csi_len = csi_end + 1; - - return EvKey; -} - -static -void -loadctrlkeys(void) -{ - int i; - for(i = 0; i < 64; i++) { - csiss3[i].sym = SymUnknown; - ss3[i].sym = SymUnknown; - ss3kpalts[i] = 0; - } - - for(i = 0; i < arrlen(csifuncs); i++) - csifuncs[i].sym = SymUnknown; - - put_csi_full(KeySym, SymUp, 0, 0, 'A'); - put_csi_full(KeySym, SymDown, 0, 0, 'B'); - put_csi_full(KeySym, SymRight,0, 0, 'C'); - put_csi_full(KeySym, SymLeft, 0, 0, 'D'); - put_csi_full(KeySym, SymBegin,0, 0, 'E'); - put_csi_full(KeySym, SymEnd, 0, 0, 'F'); - put_csi_full(KeySym, SymHome, 0, 0, 'H'); - put_csi_full(KeySym, 1, 0, 0, 'P'); - put_csi_full(KeySym, 2, 0, 0, 'Q'); - put_csi_full(KeySym, 3, 0, 0, 'R'); - put_csi_full(KeySym, 4, 0, 0, 'S'); - - put_csi_full(KeySym, SymTab, ModShift, ModShift, 'Z'); - - put_ss3_kpalt(KeySym, SymKpenter, 'M', 0); - put_ss3_kpalt(KeySym, SymKpequals, 'X', '='); - put_ss3_kpalt(KeySym, SymKpmult, 'j', '*'); - put_ss3_kpalt(KeySym, SymKpplus, 'k', '+'); - put_ss3_kpalt(KeySym, SymKpcomma, 'l', ','); - put_ss3_kpalt(KeySym, SymKpminus, 'm', '-'); - put_ss3_kpalt(KeySym, SymKpperiod, 'n', '.'); - put_ss3_kpalt(KeySym, SymKpdiv, 'o', '/'); - put_ss3_kpalt(KeySym, SymKp0, 'p', '0'); - put_ss3_kpalt(KeySym, SymKp1, 'q', '1'); - put_ss3_kpalt(KeySym, SymKp2, 'r', '2'); - put_ss3_kpalt(KeySym, SymKp3, 's', '3'); - put_ss3_kpalt(KeySym, SymKp4, 't', '4'); - put_ss3_kpalt(KeySym, SymKp5, 'u', '5'); - put_ss3_kpalt(KeySym, SymKp6, 'v', '6'); - put_ss3_kpalt(KeySym, SymKp7, 'w', '7'); - put_ss3_kpalt(KeySym, SymKp8, 'x', '8'); - put_ss3_kpalt(KeySym, SymKp9, 'y', '9'); - - put_csi_func(KeySym, SymFind, 1); - put_csi_func(KeySym, SymInsert, 2); - put_csi_func(KeySym, SymDelete, 3); - put_csi_func(KeySym, SymSelect, 4); - put_csi_func(KeySym, SymPageup, 5); - put_csi_func(KeySym, SymPagedown, 6); - put_csi_func(KeySym, SymHome, 7); - put_csi_func(KeySym, SymEnd, 8); - - put_csi_func(KeyFunc, 1, 11); - put_csi_func(KeyFunc, 2, 12); - put_csi_func(KeyFunc, 3, 13); - put_csi_func(KeyFunc, 4, 14); - put_csi_func(KeyFunc, 5, 15); - put_csi_func(KeyFunc, 6, 17); - put_csi_func(KeyFunc, 7, 18); - put_csi_func(KeyFunc, 8, 19); - put_csi_func(KeyFunc, 9, 20); - put_csi_func(KeyFunc, 10, 21); - put_csi_func(KeyFunc, 11, 23); - put_csi_func(KeyFunc, 12, 24); - put_csi_func(KeyFunc, 13, 25); - put_csi_func(KeyFunc, 14, 26); - put_csi_func(KeyFunc, 15, 28); - put_csi_func(KeyFunc, 16, 29); - put_csi_func(KeyFunc, 17, 31); - put_csi_func(KeyFunc, 18, 32); - put_csi_func(KeyFunc, 19, 33); - put_csi_func(KeyFunc, 20, 34); - - do_csi['u' - 0x40] = &do_csi_u; - do_csi['M' - 0x40] = &do_csi_m; - do_csi['m' - 0x40] = &do_csi_m; - do_csi['R' - 0x40] = &do_csi_R; - - do_csi['y' - 0x40] = &do_csi_y; - - csiinit = 1; -} - -static -enum KeyEvent -peekcsi(Input *in, ulong introlen, Key *key, int force, ulong *nb) -{ - ulong csi_len; - long arg[16]; - ulong nargs = arrlen(arg); - ulong cmd; - - enum KeyEvent ev = parse_csi(in, introlen, &csi_len, &nargs, arg, &cmd); - - if (ev== EvAgain) { - if(!force) - return ev; - - emitcodepoint(in, '[', key); - key->mods |= ModAlt; - *nb = introlen; - return EvKey; - } - // Mouse in X10 encoding consumes the next 3 bytes also - if (cmd == 'M' && nargs < 3) { - in->buf.b += csi_len; - ev = peekmousekey(in, key, nb); - in->buf.b -= csi_len; - - if (ev == EvKey) - *nb += csi_len; - - return ev; - } - - ev = EvNil; - - // We know from the logic above that cmd must be >= 0x40 and < 0x80 - if (do_csi[(cmd & 0xff) - 0x40]) - ev = (*do_csi[(cmd & 0xff) - 0x40])(in, key, cmd, nargs, arg); - - if (ev == EvNil) { - key->type = KeyUnknownCSI; - key->code.num = cmd; - key->mods = 0; - - in->buf.off = csi_len - introlen; - *nb = introlen; /* dont advance yet */ - return EvKey; - } - - *nb = csi_len; - return ev; -} - -static -enum KeyEvent -peekss3(Input *in, ulong introlen, Key *key, int force, ulong *nb) -{ - if(bufcount(in) < introlen + 1) { - if(!force) - return EvAgain; - - emitcodepoint(in, 'O', key); - key->mods |= ModAlt; - *nb= bufcount(in); - return EvKey; - } - uchar cmd = in->buf.b[introlen]; - - if(cmd < 0x40 || cmd >= 0x80) - return EvNil; - - key->type = csiss3[cmd - 0x40].type; - key->code.sym = csiss3[cmd - 0x40].sym; - key->mods = csiss3[cmd - 0x40].modset; - - if (key->code.sym == SymUnknown) { - if (in->flags & FlagConvertKP && ss3kpalts[cmd - 0x40]) { - key->type = KeyUnicode; - key->code.pt = ss3kpalts[cmd - 0x40]; - key->mods = 0; - - key->utf8[0] = key->code.pt; - key->utf8[1] = 0; - } else { - key->type = ss3[cmd - 0x40].type; - key->code.sym = ss3[cmd - 0x40].sym; - key->mods = ss3[cmd - 0x40].modset; - } - } - - if(key->code.sym == SymUnknown) - return EvNil; - - *nb = introlen + 1; - - return EvKey; -} - -static -enum KeyEvent -peekctrl(Input *in, ulong introlen, Key *key, int force, ulong *nb) -{ - ulong str_end = introlen; - - while(str_end < bufcount(in)) { - if (in->buf.b[str_end] == 0x9c) // ST - break; - if (in->buf.b[str_end] == 0x1b && - (str_end + 1) < bufcount(in) && - in->buf.b[str_end+1] == 0x5c) // ESC-prefixed ST - break; - - str_end++; - } - - if (str_end >= bufcount(in)) - return EvAgain; - - *nb = str_end + 1; - if(in->buf.b[str_end] == 0x1b) - (*nb)++; - - // XXX: read carefully - if(in->savedcsi) - free(in->savedcsi); - - ulong len = str_end - introlen; - - in->nsavedcsi++; - in->savedcsi = malloc(len + 1); - - strncpy(in->savedcsi, (char *)in->buf.b + introlen, len); - in->savedcsi[len] = 0; - - key->type = (in->buf.b[introlen-1] & 0x1f) == 0x10 ? KeyDCS : KeyOSC; - key->code.num = in->nsavedcsi; - key->mods = 0; - - return EvKey; -} - -static -enum KeyEvent -peekesc(Input *in, Key *key, int force, ulong *nb) -{ - if (bufcount(in) == 0) - return in->closed ? EvEOF : EvNil; - - switch (*in->buf.b) { - case 0x1b: - if(bufcount(in) < 2) - return EvNil; - - switch(in->buf.b[1]) { - case 0x4f: // ESC-prefixed SS3 - return peekss3(in, 2, key, force, nb); - - case 0x50: // ESC-prefixed DCS - case 0x5d: // ESC-prefixed OSC - return peekctrl(in, 2, key, force, nb); - - case 0x5b: // ESC-prefixed CSI - return peekcsi(in, 2, key, force, nb); - } - - return EvNil; - - case 0x8f: // SS3 - return peekss3(in, 1, key, force, nb); - - case 0x90: // DCS - case 0x9d: // OSC - return peekctrl(in, 1, key, force, nb); - - case 0x9b: // CSI - return peekcsi(in, 1, key, force, nb); - } - - return EvNil; -} - - -// ----------------------------------------------------------------------- -// internal functions - -static -int -registername(Input *in, int sym, char *name) -{ - if (!sym) - sym = in->nkeynm; - - if(sym >= in->nkeynm) { - char **tmp = realloc(in->keynm, sizeof(*tmp) * (sym + 1)); - if(!tmp) - return -1; - - in->keynm = tmp; - - // Fill in the hole - for(int i = in->nkeynm; i < sym; i++) - in->keynm[i] = nil; - - in->nkeynm = sym + 1; - } - - in->keynm[sym] = name; - - return sym; -} - -static -int -registerc0(Input *in, int sym, int modset, int modmask, uchar ctrl, char *name) -{ - if(ctrl >= 0x20) { - errno = EINVAL; - return -1; - } - - if (name) - sym = registername(in, sym, name); - - in->c0[ctrl].sym = sym; - in->c0[ctrl].modset = modset; - in->c0[ctrl].modmask = modmask; - - return sym; -} - -static -void -advance(Input *in, ulong nb) -{ - if (in->buf.c < in->buf.b + nb) { - in->buf.b = in->buf.c; - return; - } - - in->buf.b += nb; -} - -static -void -slidebuffer(Input *in) -{ - static const ulong halfway = arrlen(in->buf.bytes) / 2; - if (in->buf.b - in->buf.bytes > halfway) { - memmove(in->buf.bytes, in->buf.bytes + halfway, halfway); - in->buf.b -= halfway; - in->buf.c -= halfway; - } -} - -static -void -harmonize(Input *in, Key *key) -{ - int flags = in->hflag; - - if (flags & HarmonizeSpace) { - if (key->type == KeyUnicode && key->code.pt == 0x20) { - key->type = KeySym; - key->code.sym = SymSpace; - } - } else { - if (key->type == KeySym && key->code.sym == SymSpace) { - key->type = KeyUnicode; - key->code.pt = 0x20; - utf8·runetobyte((char*)key->utf8, &key->code.pt); - } - } - - if (flags & HarmonizeDelBS) { - if (key->type == KeySym && key->code.sym == SymDel) { - key->code.sym = SymBackspace; - } - } -} - -static -void -emitcodepoint(Input *in, rune r, Key *key) -{ - if (r == 0) { - key->type = KeySym; - key->code.sym = SymSpace; - key->mods = ModCtrl; - goto harmonize; - } - if (r < 0x20) { - key->code.pt = 0; - key->mods = 0; - if (!(in->flags & FlagNoInterpret) && in->c0[r].sym != SymUnknown) { - key->code.sym = in->c0[r].sym; - key->mods |= in->c0[r].modset; - } - if (!key->code.sym) { - key->type = KeyUnicode; - if (r+0x40 >= 'A' && r+0x40 <= 'Z') - // it's a letter - use lowercase instead - key->code.pt = r + 0x60; - else - key->code.pt = r + 0x40; - key->mods = ModCtrl; - } else - key->type = KeySym; - goto harmonize; - } - if (r == 0x7f && !(in->flags & FlagNoInterpret)) { - // ascii del - key->type = KeySym; - key->code.sym = SymDel; - key->mods = 0; - goto harmonize; - } - if (r >= 0x20 && r < 0x80) { - // ascii lowbyte range - key->type = KeyUnicode; - key->code.pt = r; - key->mods = 0; - goto harmonize; - } - if (r >= 0x80 && r < 0xa0) { - // UTF-8 never starts with a C1 byte. So we can be sure of these - key->type = KeyUnicode; - key->code.pt = r - 0x40; - key->mods = ModCtrl|ModAlt; - goto harmonize; - } - key->type = KeyUnicode; - key->code.pt = r; - key->mods = 0; - -harmonize: - harmonize(in, key); - utf8·runetobyte((char*)key->utf8, &key->code.pt); -} - -static -enum KeyEvent -peekmousekey(Input *in, Key *key, ulong *nb) -{ - if (in->buf.c - in->buf.b < 3) - return EvAgain; - - key->type = KeyMouse; - key->code.mouse[0] = in->buf.c[0] - 0x20; - key->code.mouse[1] = in->buf.c[1] - 0x20; - key->code.mouse[2] = in->buf.c[2] - 0x20; - key->code.mouse[3] = 0; - - key->mods = (key->code.mouse[0] & 0x1c) >> 2; - key->code.mouse[0] &= ~0x1c; - - *nb = 3; - return EvKey; -} - -enum KeyEvent peekkey(Input *in, Key *key, int force, ulong *nb); - -static -enum KeyEvent -peeksimplekey(Input *in, Key *key, int force, ulong *nb) -{ - uchar c, *b; - int n; - rune r; - enum KeyEvent ev; - - b = in->buf.b; - c = *b; - - if (c == 0x1b) { - if (bufcount(in) == 1) { - if (!force) - return EvAgain; - goto ascii; - } - in->buf.b++; - ev = peekkey(in, key, force, nb); - in->buf.b--; - - switch (ev) { - case EvKey: - key->mods |= ModAlt; - (*nb)++; - /* fallthrough */ - case EvNil: case EvEOF: - case EvAgain: case EvErr: - return ev; - } - } - if (c == 0xa0) - goto ascii; - if (in->flags & FlagUTF8) { - n = utf8·bytetorune(&r, (char*)in->buf.b); - *nb = n; /* store the number of bytes */ - if (n > bufcount(in)) { - if (!force) - return EvAgain; - r = RuneErr; - *nb = bufcount(in); - } - key->type = KeyUnicode; - key->mods = 0; - goto utf8; - } - /* if we are here just emit raw byte */ - key->type = KeyUnicode; - key->code.pt = c; - key->mods = 0; - key->utf8[0] = c; - key->utf8[1] = 0; - *nb = 1; - return EvKey; - -ascii: - *nb = 1; - r = c; -utf8: - emitcodepoint(in, r, key); - return EvKey; -} - -// ----------------------------------------------------------------------- -// exported functions - -Input * -makeinput(int fd, int flags, unibi_term *info) -{ - int i; - Input *in; - char *e; - - if (!(in = malloc(sizeof(in)))) - panicf("out of memory"); - - in->fd = fd; - if (!(flags & (FlagRaw|FlagUTF8))) { - if (((e = getenv("LANG")) || (e = getenv("LC_MESSAGES")) || (e = getenv("LC_ALL"))) && - (e = strchr(e, '.')) && e++ && (!stricmp(e, "UTF-8") || !stricmp(e, "UTF8"))) - flags |= FlagUTF8; - else - flags |= FlagRaw; - } - in->flags = flags; - in->wait = 50; /* in msec */ - in->closed = 0; - in->started = 0; - in->hasold = 0; - in->keys = loadtermkeys(info); - - /* initialize buffer */ - in->buf.c = in->buf.b = in->buf.bytes; - in->buf.e = arrend(in->buf.bytes); - in->buf.off = 0; - memset(in->buf.bytes, 0, arrlen(in->buf.bytes)); - - /* initialize names */ - for (i = 0; i < 32; i++) - in->c0[i].sym = SymNone; - - registerc0(in, SymTab, 0x09, 0, 0, nil); - registerc0(in, SymEnter, 0x0d, 0, 0, nil); - registerc0(in, SymEscape, 0x1b, 0, 0, nil); - - /* load in csi */ - in->nsavedcsi = 0; - in->savedcsi = nil; - loadctrlkeys(); - - return in; -} - -void -freeinput(Input *in) -{ - // free(in); -} - -int -startrecord(Input *in) -{ - struct termios new; - if (in->started) - return 1; - - if (in->fd != -1 && !(in->flags & FlagNoTermIOS)) { - if (tcgetattr(in->fd, &new) == 0) { - in->oldterm = new; - in->hasold = 1; - - new.c_iflag &= ~(IXON|INLCR|ICRNL); - new.c_lflag &= ~(ICANON|ECHO|IEXTEN); - - new.c_cc[VMIN] = 1; - new.c_cc[VTIME] = 0; - - if (in->flags & FlagCtrlC) - new.c_lflag &= ~ISIG; - else { - /* Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT */ - new.c_cc[VQUIT] = _POSIX_VDISABLE; - new.c_cc[VSUSP] = _POSIX_VDISABLE; - /* Some OSes have Ctrl-Y==VDSUSP */ -# ifdef VDSUSP - new.c_cc[VDSUSP] = _POSIX_VDISABLE; -# endif - } - tcsetattr(in->fd, TCSANOW, &new); - } - } - - in->started = 1; - return 1; -} - -int -stoprecord(Input *in) -{ - if (!in->started) - return 1; - - if (in->hasold) - tcsetattr(in->fd, TCSANOW, &in->oldterm); - - in->started = 0; - return 1; -} - -enum KeyEvent -peekkey(Input *in, Key *key, int force, ulong *nb) -{ - int i, again = 0; - enum KeyEvent ev; - static enum KeyEvent (*peek[2])(Input *, Key *, int, ulong *) = { peeksym, peekesc }; - - if (!in->started) { - errno = EINVAL; - return EvErr; - } - - if (in->buf.off) { - in->buf.b += in->buf.off; - in->buf.off = 0; - } - - for (i = 0; i < arrlen(peek); i++) { - ev = peek[i](in, key, force, nb); - switch (ev) { - case EvKey: - slidebuffer(in); - /* fallthrough */ - case EvEOF: - case EvErr: - return ev; - case EvAgain: - if (!force) - again = 1; - /* fallthrough */ - case EvNil: - continue; - } - } - if (again) - return EvAgain; - - return peeksimplekey(in, key, force, nb); -} - -enum KeyEvent -getkey(Input *in, Key *key) -{ - ulong nb; - enum KeyEvent ev; - - ev = peekkey(in, key, 0, &nb); - switch (ev) { - case EvKey: - advance(in, nb); - break; - case EvAgain: - peekkey(in, key, 1, &nb); - /* get nb but don't advance */ - break; - default: - ; - } - return ev; -} - -enum KeyEvent -demandkey(Input *in, Key *key) -{ - ulong nb; - enum KeyEvent ev; - - ev = peekkey(in, key, 1, &nb); - if (ev == EvKey) - advance(in, nb); - - return ev; -} - -enum KeyEvent -isreadablekey(Input *in) -{ - int n; - if (in->fd == -1) { - errno = EBADF; - return EvErr; - } - - /* reset to beginning of buffer */ - if (in->buf.b > in->buf.bytes) { - n = in->buf.b - in->buf.bytes; - memmove(in->buf.bytes, in->buf.b, n); - in->buf.b = in->buf.bytes; - in->buf.c = in->buf.b + n; - } - -read: - n = read(in->fd, in->buf.c, in->buf.e-in->buf.c); - if (n == -1) { - if (errno == EAGAIN) - return EvNil; - if (errno == EINTR && !(in->flags & FlagEintr)) - goto read; - else - return EvErr; - } - if (n < 1) { - in->closed = 1; - return EvNil; - } - in->buf.c += n; - return EvAgain; -} - -enum KeyEvent -waitkey(Input *in, Key *key) -{ - enum KeyEvent ev; - struct pollfd p; - - if (in->fd == -1) { - errno = EBADF; - return EvErr; - } - - for (;;) { - ev = getkey(in, key); - switch (ev) { - case EvKey: case EvEOF: case EvErr: - return ev; - case EvNil: - ev = isreadablekey(in); - if (ev == EvErr) - return ev; - break; - - case EvAgain: - /* can't wait any longer */ - if (in->closed) - return demandkey(in, key); - poll: - p.fd = in->fd; - p.events = POLLIN; - - if (poll(&p, 1, in->wait) == -1) { - if (errno == EINTR && !(in->flags & FlagEintr)) - goto poll; - return EvErr; - } - - if (p.revents & (POLLIN|POLLHUP|POLLERR)) - ev = isreadablekey(in); - else - ev = EvNil; - - if (ev == EvErr) - return ev; - if (ev == EvNil) - return demandkey(in, key); - break; - } - } - /* unreachable */ -} - -enum KeyEvent -decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col) -{ - if(key->type != KeyMouse) - return EvNil; - - if(button) - *button = 0; - - getpos(key, row, col); - - if(!ev) - return EvKey; - - int btn = 0; - int code = key->code.mouse[0]; - int drag = code & 0x20; - code &= ~0x3c; - - switch(code) { - case 0: - case 1: - case 2: - *ev = drag ? MouseDrag : MousePress; - btn = code + 1; - break; - - case 3: - *ev = MouseRelease; - // no button hint - break; - - case 64: - case 65: - *ev = drag ? MouseDrag : MousePress; - btn = code + 4 - 64; - break; - - default: - *ev = MouseNil; - } - - if (button) - *button = btn; - - if (key->code.mouse[3] & 0x80) - *ev = MouseRelease; - - return EvKey; -} - -enum KeyEvent -decodepos(Input *in, Key *key, int *row, int *col) -{ - if (key->type != KeyPosition) - return EvNil; - - getpos(key, row, col); - - return EvKey; -} - -enum KeyEvent -decodemode(Input *in, Key *key, int *init, int *mode, int *val) -{ - if (key->type != KeyModeReport) - return EvNil; - - if (init) - *init = key->code.mouse[0]; - - if (mode) - *mode = (key->code.mouse[1] << 8) | key->code.mouse[2]; - - if (val) - *val = key->code.mouse[3]; - - return EvKey; -} - -char * -keyname(Input *in, int sym) -{ - if (sym == SymUnknown || sym >= in->nkeynm) - return ""; - - return in->keynm[sym]; -} - -// ----------------------------------------------------------------------- -// main point of entry - -static -void -printkey(Input *in, Key *key) -{ - enum MouseEvent ev; - int button, line, col; - int init, mode, val; - - switch(key->type) { - case KeyUnicode: - fprintf(stderr, "Unicode codepoint=U+%04dx utf8='%s'", key->code.pt, key->utf8); - break; - case KeyFunc: - fprintf(stderr, "Function F%d", key->code.num); - break; - case KeySym: - fprintf(stderr, "Keysym sym=%d(%s)", key->code.sym, keyname(in, key->code.sym)); - break; - case KeyMouse: - decodemouse(in, key, &ev, &button, &line, &col); - fprintf(stderr, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev, button, line, col); - break; - case KeyPosition: - decodepos(in, key, &line, &col); - fprintf(stderr, "Position report pos=(%d,%d)\n", line, col); - break; - case KeyModeReport: - decodemode(in, key, &init, &mode, &val); - fprintf(stderr, "Mode report mode=%s %d val=%d\n", init == '?' ? "DEC" : "ANSI", mode, val); - break; - case KeyDCS: - fprintf(stderr, "Device Control String"); - break; - case KeyOSC: - fprintf(stderr, "Operating System Control"); - break; - case KeyUnknownCSI: - fprintf(stderr, "unknown CSI\n"); - break; - } - - mode = key->mods; - fprintf(stderr, " mod=%s%s%s+%02x", - (mode & ModCtrl ? "" : ""), - (mode & ModAlt ? "" : ""), - (mode & ModShift? "" : ""), - mode & ~(ModCtrl|ModAlt|ModShift)); -} - -int -main() -{ - char *name; - Input *in; - unibi_term *info; - Key key; - enum KeyEvent ev; - - name = getenv("TERM"); - info = unibi_from_term(name); - in = makeinput(0, FlagSpaceSymbol, info); - - int n = 0; /* for debugging purposes only */ - startrecord(in); - while ((ev = waitkey(in, &key)) != EvEOF) { - switch (ev) { - case EvKey: - printkey(in, &key); - printf("\n"); - break; - case EvNil: - printf("\n"); - case EvAgain: - printf("\n"); - default: - ; - } - n++; - if (n > 200) - break; - } - stoprecord(in); - freeinput(in); -} diff --git a/sys/libterm/input.c b/sys/libterm/input.c deleted file mode 100644 index 038e8df..0000000 --- a/sys/libterm/input.c +++ /dev/null @@ -1,108 +0,0 @@ -#include -#include -#include - -#define iota(x) 1 << (x) - -// ----------------------------------------------------------------------- -// enums - -enum -{ - KeyRune, KeyFunc, KeySym, - KeyMouse, KeyPosition, KeyModeReport, - KeyDCS, KeyOSC, /* add other recognised types here */ - KeyUnknownCSI = -1 -}; - -enum KeyEvent -{ - EvNil, EvKey, EvEOF, EvAgain, EvErr, -}; - -enum -{ - SymUnknown = -1, - SymNone = 0, - - /* special names in c0 */ - SymBackspace, SymTab, SymEnter, SymEscape, - - /* special names in g0 */ - SymSpace, SymDel, - - /* special keys */ - SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert, - SymDelete, SymSelect, SymPageUp, SymPageDown, SymHome, SymEnd, - - /* special keys from terminfo */ - SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit, - SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions, - SymPrint, SymRedo, SymReference, SymRefresh, SymReplace, - SymRestart, SymResume, SymSave, SymSuspend, SymUndo, - - /* numeric keypad special keys */ - SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8, - SymKp9, SymKpEnter, SymKpPlus, SymKpMinus, SymKpMul, SymKpDiv, SymKpComma, - SymKpDot, SymKpEq, - - /* et cetera ad nauseum */ - NumSyms -}; - -enum MouseEvent -{ - MouseNil, MousePress, MouseDrag, MouseRelease, -}; - -enum -{ - ModShift = iota(0), - ModAlt = iota(1), - ModCtrl = iota(2), -}; - -// ----------------------------------------------------------------------- -// types - -typedef struct Input Input; - -struct Input -{ - int fd; - int wait; /* in msec */ - struct { - char *b, *c, bytes[1024]; - } rbuf; /* read buffer */ - struct { - char *b, *c, bytes[1024]; - } ebuf; /* escape buffer */ -}; - -// ----------------------------------------------------------------------- -// globals - -// ----------------------------------------------------------------------- -// utility functions - -// ----------------------------------------------------------------------- -// implementation - -Input * -makeinput(int fd, int flags, unibi_term *info) -{ - int i; - Input *in; - char *e; - - if (!(in = calloc(1, sizeof(in)))) - panicf("out of memory"); - - in->fd = fd; - in->wait = 50; /* in msec */ - - /* initialize buffers */ - in->rbuf.c = in->rbuf.b = in->rbuf.bytes; - - return in; -} diff --git a/sys/libterm/term.c b/sys/libterm/term.c index cda2bbe..11591fc 100644 --- a/sys/libterm/term.c +++ b/sys/libterm/term.c @@ -1,5 +1,8 @@ #include "term.h" +#include +#include + struct ExtraInfo { char *enteralt; @@ -19,6 +22,8 @@ struct ExtraInfo vt200 = .exitmouse = "\e[?1002l\e[?1006l", }; +static Term *sigwinchhead; + // ----------------------------------------------------------------------- // database lookup @@ -78,26 +83,9 @@ guessextrastr(Term *t, char *name, char *guess) return guess; } -static -int -tryextrabool(Term *t, char *name) -{ - const char *nm; - size_t max = unibi_count_ext_bool(t->info); - for (size_t i = 0; i < max; i++) { - nm = unibi_get_ext_bool_name(t->info, i); - if (nm && !strcmp(nm, name)) { - return (int)i; - } - } - return -1; -} - /* formats escape strings and writes to output */ static void tfmt(Term *t, char *esc, int n, ...); - -// ----------------------------------------------------------------------- -// exported pen methods +static void tclear(Term *t); // ----------------------------------------------------------------------- // exported term methods @@ -113,14 +101,25 @@ ttmpbuf(Term *t, int len) return (t->tmp.b = realloc(t->tmp.b, len)); } -void -tinit(Term *t) +void twrite(Term *t, long len, char *s); +void tlistensigwinch(Term *t); + +Term* +tmake(void) { + Term *t; + + t = calloc(1, sizeof(*t)); + + /* meta data */ t->name = getenv("TERM"); t->info = unibi_from_term(t->name); if (!t->info) panicf("could not identify terminal"); + t->fd = 1; // stdout + tlistensigwinch(t); + t->mode.mouse = 0; t->mode.cursorvis = 1; t->mode.altscreen = 0; @@ -128,13 +127,23 @@ tinit(Term *t) t->cap.colors = unibi_get_num(t->info, unibi_max_colors); t->cap.bce = unibi_get_bool(t->info, unibi_back_color_erase); - /* TODO */ - t->input = nil; - t->root = nil; - t->pen = (Pen){0}; + /* initialize root window (get current size)*/ + struct winsize ws = { 0 }; + if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1) + goto bad; - /* fill in buffers */ - t->buf.c = t->buf.b; + t->root = wmake(nil, 0, 0, ws.ws_col, ws.ws_row, 0); + + t->root->curvis = 1; + t->root->blink = 0; + + t->pen = (Pen){ + .state = PenNormal, + .col = {.fg = -1, .bg = -1}, + }; + + /* fill in output buffers */ + t->buf.c = t->buf.b; t->tmp.b = nil; t->tmp.len = 0; @@ -174,22 +183,16 @@ tinit(Term *t) t->esc.ext.rgbf = guessextrastr(t, "setrgbf", "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); t->esc.ext.rgbb = guessextrastr(t, "setrgbb", "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); - /* acs characters */ - t->acs.vline = tryinfostr(t, unibi_acs_vline); - t->acs.hline = tryinfostr(t, unibi_acs_hline); - t->acs.plus = tryinfostr(t, unibi_acs_plus); - t->acs.ltee = tryinfostr(t, unibi_acs_ltee); - t->acs.rtee = tryinfostr(t, unibi_acs_rtee); - t->acs.ttee = tryinfostr(t, unibi_acs_ttee); - t->acs.btee = tryinfostr(t, unibi_acs_btee); - t->acs.ulcorner = tryinfostr(t, unibi_acs_ulcorner); - t->acs.urcorner = tryinfostr(t, unibi_acs_urcorner); - t->acs.llcorner = tryinfostr(t, unibi_acs_llcorner); - t->acs.lrcorner = tryinfostr(t, unibi_acs_lrcorner); + return t; + +bad: + panicf("failed to initialize terminal instance"); + free(t); + return nil; } void -tfini(Term *t) +tfree(Term *t) { if (t->mode.mouse) twrite(t, 0, vt200.exitmouse); @@ -199,6 +202,56 @@ tfini(Term *t) twrite(t, 0, vt200.exitalt); tfmt(t, t->esc.sgr0, 0); + tclear(t); + free(t); +} + +/* handle resize events */ +void +tresize(Term *t) +{ + if (t->fd == -1) + return; + + struct winsize ws = { 0 }; + if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1) + return; + + printf("[%d,%d]\n", ws.ws_col, ws.ws_row); + if (t->root->w != ws.ws_col || t->root->h != ws.ws_row) + wresize(t->root, ws.ws_col, ws.ws_row); +} + +static +void +sigwinch(int num) +{ + Term *it; + for (it = sigwinchhead; it; it = it->link) + tresize(it); +} + +void +tlistensigwinch(Term *t) +{ + sigset_t new, old; + Term *it; + + sigemptyset(&new); + sigaddset(&new, SIGWINCH); + sigprocmask(SIG_BLOCK, &new, &old); + + if (!sigwinchhead) { + sigaction(SIGWINCH, &(struct sigaction){ .sa_handler = sigwinch }, nil); + sigwinchhead = t; + } else { + it = sigwinchhead; + while (it->link) + it = it->link; + it->link = t; + } + + sigprocmask(SIG_SETMASK, &old, nil); } void @@ -217,12 +270,14 @@ twrite(Term *t, long len, char *s) if (!len) len = strlen(s); - while (len > 0) { - n = MIN(len, arrend(t->buf.b) - t->buf.c); - memcpy(t->buf.c, s, n); - t->buf.c += n; - len -= n; +loop: + n = MIN(len, arrend(t->buf.b) - t->buf.c); + memcpy(t->buf.c, s, n); + t->buf.c += n; + len -= n; + if (len) { tflush(t); + goto loop; } } @@ -256,8 +311,8 @@ tsetpen(Term *t, Pen new) /* fg/bg color */ /* TODO: add a check for if the terminal supports true color */ - /* TODO: add a check for negative indices */ - if (new.state & PenTrueClr) { + /* TODO: deal w/ negative indices properly */ + if (new.state & PenRGB) { tfmt(t, t->esc.ext.rgbf, 3, new.rgb.fg.r, new.rgb.fg.g, new.rgb.fg.b); tfmt(t, t->esc.ext.rgbb, 3, new.rgb.bg.r, new.rgb.bg.g, new.rgb.bg.b); } else { @@ -282,12 +337,13 @@ tfmt(Term *t, char *esc, int n, ...) panicf("no terminfo escape string given"); va_start(args, n); - for (i = 0; i < arrlen(param) && i < n; i++) + for (i = 0; i < arrlen(param) && i < n; i++) { param[i] = unibi_var_from_num(va_arg(args, int)); + } va_end(args); len = unibi_run(esc, param, c, sizeof(buf)); - if (len < arrlen(buf)) { + if (len >= arrlen(buf)) { c = ttmpbuf(t, len); unibi_run(esc, param, c, len); } @@ -296,6 +352,7 @@ tfmt(Term *t, char *esc, int n, ...) } /* absolute move */ +static int tgoto(Term *t, int row, int col) { @@ -324,6 +381,7 @@ tgoto(Term *t, int row, int col) } /* relative move */ +static void tjump(Term *t, int down, int right) { @@ -346,50 +404,86 @@ tjump(Term *t, int down, int right) tfmt(t, t->esc.cub, 1, -right); } +static void -tdel(Term *t, int num) +tclear(Term *t) { - char *c, buf[64]; - if (num < 1) - return; - - /* TODO: allow for not moving */ - if (t->cap.bce) { - tfmt(t, t->esc.ech, 1, num); - tjump(t, 0, num); - } else { - c = buf; - memset(c, ' ', arrlen(buf)); - while(num > 64) { - twrite(t, arrlen(buf), c); - num -= arrlen(buf); - } - twrite(t, num, c); - } + tfmt(t, t->esc.ed2, 0); } void -tclear(Term *t) +tblit(Term *t, Window *win) { - tfmt(t, t->esc.ed2, 0); + int r, c, n, j; + Row *row; + char u[UTFmax+1] = {0}; + + j = 0; + tgoto(t, win->top, win->left); + for (r = 0; r < win->h; r++) { + row = win->row + r; + if (!row->dirty) { + j++; + continue; + } + + if (j) { + tjump(t, j, 0); + j = 0; + } + + for (c = 0; c < win->w; c++) { + tsetpen(t, row->cells[c].pen); + n = utf8·runetobyte(u, &row->cells[c].txt); + twrite(t, n, u); + } + + row->dirty = 0; + } + + tflush(t); } // ----------------------------------------------------------------------- -// testing entry +// testing int main() { - Term t; - tinit(&t); - - printf("%s: %s\n", unibi_short_name_str(unibi_set_a_foreground), t.esc.sgr_fg); - printf("%s: %s\n", unibi_short_name_str(unibi_set_a_background), t.esc.sgr_bg); - tfmt(&t, t.esc.ext.rgbf, 3, 10, 10, 100); - tfmt(&t, t.esc.ext.rgbb, 3, 100, 0, 100); - twrite(&t, 0, "hello world\n"); - twrite(&t, 0, "hello world\n"); - twrite(&t, 0, t.acs.hline); - - tfini(&t); + int i; + Term *t; + Window *win; + + t = tmake(); + win = t->root; + tclear(t); + + win->pen = (Pen){ + .state = PenNormal, + .col = {.fg=-1, .bg=-1}, + }; + for (i = 0; i < 2000; i++) + wputrune(win, 'a'); + + tblit(t, win); + + win->cur.row = 10; + win->cur.col = 0; + + win->pen = (Pen){ + .state=PenNormal|PenRGB, + .rgb={.fg={200, 100, 100}, .bg={0, 0, 0} }, + }; + + for (i = 0; i < 500; i++) + wputrune(win, 'b'); + + tblit(t, win); + + sleep(5); + wscroll(win, 10); + tblit(t, win); + sleep(5); + + tfree(t); } diff --git a/sys/libterm/term.h b/sys/libterm/term.h index acae95f..6bd2f6b 100644 --- a/sys/libterm/term.h +++ b/sys/libterm/term.h @@ -7,165 +7,11 @@ #include #define iota(x) 1 << (x) -/* - * obtained from: - * https://invisible-island.net/ncurses/man/curs_add_wch.3x.html - */ -#define ACSRUNES \ - /* name utf8 ascii acsc*/ \ - ACSRUNE("block", 0x25ae, '#', '0') \ - ACSRUNE("board", 0x2592, '#', 'h') \ - ACSRUNE("btee", 0x2534, '+', 'v') \ - ACSRUNE("bullet", 0x00b7, 'o', '~') \ - ACSRUNE("ckboard", 0x2592, ':', 'a') \ - ACSRUNE("darrow", 0x2193, 'v', '.') \ - ACSRUNE("degree", 0x00b0, '\'','f') \ - ACSRUNE("diamond", 0x25c6, '+', '`') \ - ACSRUNE("gequal", 0x2265, '>', '>') \ - ACSRUNE("hline", 0x2500, '-', 'q') \ - ACSRUNE("antern", 0x2603, '#', 'i') \ - ACSRUNE("larrow", 0x2190, '<', ',') \ - ACSRUNE("lequal", 0x2264, '<', 'y') \ - ACSRUNE("llcorner", 0x2514, '+', 'm') \ - ACSRUNE("lrcorner", 0x2518, '+', 'j') \ - ACSRUNE("ltee", 0x2524, '+', 't') \ - ACSRUNE("nequal", 0x2260, '!', '|') \ - ACSRUNE("pi", 0x03c0, '*', '{') \ - ACSRUNE("plminus", 0x00b1, '#', 'g') \ - ACSRUNE("plus", 0x253c, '+', 'n') \ - ACSRUNE("rarrow", 0x2192, '>', '+') \ - ACSRUNE("rtee", 0x251c, '+', 'u') \ - ACSRUNE("s1", 0x23ba, '-', 'o') \ - ACSRUNE("s3", 0x23bb, '-', 'p') \ - ACSRUNE("s7", 0x23bc, '-', 'r') \ - ACSRUNE("s9", 0x23bd, '_', 's') \ - ACSRUNE("sterling", 0x00a3, 'f', '}') \ - ACSRUNE("ttee", 0x252c, '+', 'w') \ - ACSRUNE("uarrow", 0x2191, '^', '-') \ - ACSRUNE("ulcorner", 0x250c, '+', 'l') \ - ACSRUNE("urcorner", 0x2510, '+', 'k') \ - ACSRUNE("vline", 0x2502, '|', 'x') \ - /* thick versions */ \ - ACSRUNE("t_btee", 0x253b, '+', 'V') \ - ACSRUNE("t_hline", 0x2501, '-', 'Q') \ - ACSRUNE("t_llcorner", 0x2517, '+', 'M') \ - ACSRUNE("t_lrcorner", 0x251b, '+', 'J') \ - ACSRUNE("t_ltee", 0x252b, '+', 'T') \ - ACSRUNE("t_plus", 0x254b, '+', 'N') \ - ACSRUNE("t_rtee", 0x2523, '+', 'U') \ - ACSRUNE("t_ttee", 0x2533, '+', 'W') \ - ACSRUNE("t_ulcorner", 0x250f, '+', 'L') \ - ACSRUNE("t_urcorner", 0x2513, '+', 'K') \ - ACSRUNE("t_vline", 0x2503, '|', 'X') \ - /* double version */ \ - ACSRUNE("d_btee", 0x2569, '+', 'H') \ - ACSRUNE("d_hline", 0x2550, '-', 'R') \ - ACSRUNE("d_llcorner", 0x255a, '+', 'D') \ - ACSRUNE("d_lrcorner", 0x255d, '+', 'A') \ - ACSRUNE("d_ltee", 0x2560, '+', 'F') \ - ACSRUNE("d_plus", 0x256c, '+', 'E') \ - ACSRUNE("d_rtee", 0x2563, '+', 'G') \ - ACSRUNE("d_ttee", 0x2566, '+', 'I') \ - ACSRUNE("d_ulcorner", 0x2554, '+', 'C') \ - ACSRUNE("d_urcorner", 0x2557, '+', 'B') \ - ACSRUNE("d_vline", 0x2551, '|', 'Y') - -/* enums */ - -/* key symbols */ -enum -{ - SymUnknown = -1, - SymNone = 0, - - /* special names in c0 */ - SymBackspace, SymTab, SymEnter, SymEscape, - - /* special names in g0 */ - SymSpace, SymDel, - - /* special keys */ - SymUp, SymDown, SymLeft, SymRight, SymBegin, SymFind, SymInsert, - SymDelete, SymSelect, SymPageup, SymPagedown, SymHome, SymEnd, - - /* special keys from terminfo */ - SymCancel, SymClear, SymClose, SymCommand, SymCopy, SymExit, - SymHelp, SymMark, SymMessage, SymMove, SymOpen, SymOptions, - SymPrint, SymRedo, SymReference, SymRefresh, SymReplace, - SymRestart, SymResume, SymSave, SymSuspend, SymUndo, - - /* numeric keypad special keys */ - SymKp0, SymKp1, SymKp2, SymKp3, SymKp4, SymKp5, SymKp6, SymKp7, SymKp8, - SymKp9, SymKpenter, SymKpplus, SymKpminus, SymKpmult, SymKpdiv, SymKpcomma, - SymKpperiod, SymKpequals, - - /* et cetera ad nauseum */ - NumSyms -}; - -/* key type */ -enum -{ - KeyUnicode, - KeyFunc, - KeySym, - KeyMouse, - KeyPosition, - KeyModeReport, - KeyDCS, - KeyOSC, - /* add other recognised types here */ - - KeyUnknownCSI = -1 -}; - -/* key events */ -enum KeyEvent -{ - EvNil, - EvKey, - EvEOF, - EvAgain, - EvErr, -}; - -enum MouseEvent -{ - MouseNil, - MousePress, - MouseDrag, - MouseRelease, -}; - -enum -{ - ModShift = iota(0), - ModAlt = iota(1), - ModCtrl = iota(2), -}; - -enum -{ - FlagNoInterpret = iota(0), - FlagConvertKP = iota(1), - FlagRaw = iota(2), - FlagUTF8 = iota(3), - FlagNoTermIOS = iota(4), - FlagSpaceSymbol = iota(5), - FlagCtrlC = iota(6), - FlagEintr = iota(7), -}; - -enum -{ - HarmonizeSpace = iota(0), - HarmonizeDelBS = iota(1), -}; -/* types */ typedef struct RGB8 RGB8; typedef struct Pen Pen; +typedef struct Dot Dot; typedef struct Cell Cell; typedef struct Row Row; typedef struct Buffer Buffer; @@ -194,7 +40,7 @@ enum PenUnderline = iota(6), PenBlink = iota(7), /* ... */ - PenTrueClr = iota(15), + PenRGB = iota(15), }; struct Pen @@ -213,38 +59,29 @@ struct Pen }; /* outputs */ -struct Cell { - rune r; +struct Cell +{ + rune txt; Pen pen; }; -struct Row { +struct Row +{ Cell *cells; - uint dirty:1; + uint dirty : 1; }; -/* Buffer holding the current window content (as an array) as well - * as the scroll back buffer content (as a circular/ring buffer). - * - * If new content is added to terminal the view port slides down and the - * previously top most line is moved into the scroll back buffer at postion - * scroll.index. This index will eventually wrap around and thus overwrite - * the oldest lines. - * - * In the scenerio below a scroll up has been performed. That is 'scroll.above' - * lines still lie above the current view port. Further scrolling up will show - * them. Similarly 'scroll.below' is the amount of lines below the current - * viewport. - * - * The function buffer_boundary sets the row pointers to the start/end range - * of the section delimiting the region before/after the viewport. The functions - * buffer_row_{first,last} return the first/last logical row. And - * buffer_row_{next,prev} allows to iterate over the logical lines in either - * direction. +struct Dot +{ + int row, col; +}; + +/* + * scroll.top & scroll.bot are pointers into the viewport. * * scroll back buffer * - * scroll_buf->+----------------+-----+ + * scroll.buf->+----------------+-----+ * | | | ^ \ * | before | | | | * current terminal content | viewport | | | | @@ -253,39 +90,48 @@ struct Row { * ^ | | i | \ | | i | c | * | | | n | \ | | n | r | * | | v | \ | | v | o | - * r | | i | \ | | i | l / - * o | viewport | s | >|<- scroll.index | s | l \ - * w | | i | / | | i | | - * s | | b | / | after | b | s > scroll.below + * | | i | \ | | i | l / + * | buffer | s | >|<- scroll.index | s | l \ + * h | | i | / | | i | | + * | | b | / | after | b | s > scroll.below * | | l | / | viewport | l | i | * v | | e | / | | e | z / * +----------------+-----+/ | unused | | e - * <- maxcols -> | scroll back | | - * <- cols -> | buffer | | | + * <- maxw -> | scroll back | | + * <- w -> | buffer | | | * | | | | * | | | v - * roll_buf + scroll.size->+----------------+-----+ - * <- maxcols -> - * <- cols -> + * scroll.buf + scroll.size->+----------------+-----+ + * <- maxw -> + * <- w -> */ -struct Buffer { - Row *row; /* array of Row pointers of size 'rows' */ - Row *crow; /* row on which the cursor currently resides */ - bool *tabs; /* a boolean flag for each column whether it is a tab */ +struct Buffer +{ + int w, h; /* dimension of buffer */ + Pen pen; /* default attributes */ + int maxw; /* allocated cells (maximal cols over time) */ + Row *row; /* array of row pointers of size 'h' */ struct { - Row *buf; /* a ring buffer holding the scroll back content */ - Row *top; /* row in lines where scrolling region starts */ - Row *bot; /* row in lines where scrolling region ends */ - int size; /* maximal capacity of scroll back buffer (in lines) */ - int index; /* current index into the ring buffer */ - int above; /* number of lines above current viewport */ - int below; /* number of lines below current viewport */ + Row *buf; + Row *top; + Row *bot; + int size; + int index; + int above; + int below; } scroll; - Pen pen, spen; - int nrow, ncol; /* current dimension of buffer */ - int maxcols; /* allocated cells (maximal cols over time) */ - int srow, scol; /* saved cursor row/column (zero based) */ + Dot cur, save; /* cursor position within buffer */ +}; + +struct Window +{ + struct Buffer; + int top, left; + uchar curvis : 1; + uchar blink : 2; + + Window *parent, *child, *link; }; /* input */ @@ -310,61 +156,57 @@ struct KeyInfo int modset; }; -/* make opaque? */ struct Input { int fd; int flags; - int hflag; int wait; /* in ms */ + /* modifiers */ + uchar closed : 1; + uchar started : 1; + uchar hasold : 1; + struct termios oldterm; /* buffer */ struct { long off; uchar *b, *c, *e, bytes[256]; - } buf; - - /* modifiers */ - char closed : 1; - char started : 1; - char hasold : 1; + } rbuf; + struct { + uchar *s, bytes[256]; + } ebuf; /* key data */ Node *keys; struct KeyInfo c0[32]; - - char **keynm; - int nkeynm; - - int nsavedcsi; - char *savedcsi; }; + struct Term { /* meta data */ char *name; unibi_term *info; struct { - uint altscreen : 1; - uint cursorvis : 1; - uint mouse : 1; + uchar altscreen : 1; + uchar cursorvis : 1; + uchar mouse : 1; } mode; struct { - uint bce : 1; - int colors; + uchar bce : 1; + int colors; } cap; /* input capture */ - Input *input; + Input input; /* output display */ Window *root; Pen pen; - /* output raw text */ + /* raw text to pty */ int fd; struct { char *c, b[512]; @@ -416,74 +258,13 @@ struct Term } ext; } esc; - /* basic shapes */ - struct { - rune block; - rune board; - rune hline; - rune vline; - rune plus; - rune ltee; - rune rtee; - rune ttee; - rune btee; - rune ulcorner; - rune urcorner; - rune llcorner; - rune lrcorner; - } acs; + Term *link; }; -/* - * exported functions - */ +/* functions */ +void tresize(Term *t); -#if 0 -/* buffer.c */ -void zero(Row *row, int start, int len); -void roll(Row *start, Row *end, int count); - -void bclear(Buffer *b); -void bfree(Buffer *b); -void bscroll(Buffer *b, int s); -void bresize(Buffer *b, int rows, int cols); -bool binit(Buffer *b, int rows, int cols, int size); -void brender(Buffer *b, Term *t); -void bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) ; -Row *browfirst(Buffer *b); -Row *browlast(Buffer *b); -Row *brownext(Buffer *b, Row *row); -Row *bprevrow(Buffer *b, Row *row); - -/* input.c */ -Input *makeinput(int fd, int flags, unibi_term *info); -void freeinput(Input *in); -int startrecord(Input *in); -int stoprecord(Input *in); -char *keyname(Input *in, int sym); - -enum KeyEvent term·waitkey(Input *in, Key *key); /* block until next keypress */ -enum KeyEvent term·getkey(Input *in, Key *key); /* grab key if we can */ -enum KeyEvent term·demandkey(Input *in, Key *key); /* grab now and interpret as best we can */ - -/* unpack key event into useful data */ -enum KeyEvent decodemouse(Input *in, Key *key, enum MouseEvent *ev, int *button, int *row, int *col); -enum KeyEvent decodepos(Input *in, Key *key, int *row, int *col); -enum KeyEvent decodemode(Input *in, Key *key, int *init, int *mode, int *val); -#endif - -/* term.c */ -void term·init(Term *t); -void term·fini(Term *t); -void term·flush(Term *t); -void term·write(Term *t, long len, char *s); -int term·goto(Term *t, int row, int col); -void term·jump(Term *t, int down, int right); -void term·del(Term *t, int num); -void term·setpen(Term *t, Pen pen); -void term·clear(Term *t); - -/* input.c */ -enum KeyEvent term·waitkey(Term *t, Key *key); /* block until next keypress */ -enum KeyEvent term·getkey(Term *t, Key *key); /* grab key if we can */ -enum KeyEvent term·demandkey(Term *t, Key *key); /* grab now and interpret as best we can */ +Window *wmake(Window *root, int top, int left, int w, int h, int scroll); +void wresize(Window *root, int w, int h); +void wputrune(Window *win, rune r); +void wscroll(Window *win, int s); diff --git a/sys/libterm/window.c b/sys/libterm/window.c index fec3997..5d36c8b 100644 --- a/sys/libterm/window.c +++ b/sys/libterm/window.c @@ -1,44 +1,408 @@ -#include -#include - #include "term.h" -typedef struct Rect Rect; +// ----------------------------------------------------------------------- +// buffers -struct Rect +static +void +zero(Row *row, int start, int len) { - int top, left, rows, cols; -}; + int i; + Cell cell = { + .txt = L' ', + .pen = { + .state = PenNormal, + .col.fg = -1, + .col.bg = -1, + }, + }; + + for (i = start; i < len + start; i++) + row->cells[i] = cell; + row->dirty = 1; +} -struct Window +static +void +roll(Row *start, Row *end, int count) { - Buffer buffer[2], *buf; - Rect area; /* on screen */ - Pen pen, spen; /* current and saved pen */ - uint curvis : 1; - uint damage : 1; -}; + int n = end - start; -/* functions */ + /* enforce circularity */ + count %= n; + if (count < 0) + count += n; -Window * -makewindow(Window *root, Rect area, int history) + if (count) { + char buf[count * sizeof(Row)]; /* XXX: remove VLA */ + memcpy(buf, start, count * sizeof(Row)); + memmove(start, start + count, (n - count) * sizeof(Row)); + memcpy(end - count, buf, count * sizeof(Row)); + + for (Row *row = start; row < end; row++) + row->dirty = 1; + } +} + +/* buffer operations */ +static +void +bclear(Buffer *b) { - Window *w; - w = calloc(1, sizeof(*w)); - if (!w) - panicf("out of memory"); + int i; + Cell cell = { + .txt = L' ', + .pen = { + .state = PenNormal, + .col.fg = -1, + .col.bg = -1, + }, + }; + + for (i = 0; i < b->h; i++) { + Row *row = b->row + i; + for (int j = 0; j < b->w; j++) { + row->cells[j] = cell; + row->dirty = 1; + } + } +} - w->pen = (Pen) { - .state = PenNormal, - .col = {-1, -1}, - }; +static +void +bfini(Buffer *b) +{ + int i; - if (!binit(w->buffer+0, area.rows, area.cols, history) || - !binit(w->buffer+1, area.rows, area.cols, 0)) { - free(w); - return nil; + for (i = 0; i < b->h; i++) + free(b->row[i].cells); + + free(b->row); + + if (b->scroll.size) { + for (i = 0; i < b->scroll.size; i++) + free(b->scroll.buf[i].cells); + + free(b->scroll.buf); + } +} + +static +void +bscroll(Buffer *b, int s) +{ + Row tmp; + int i, ssz = b->scroll.bot - b->scroll.top; + + /* work in quanta of screen size */ + if (s > ssz) { + bscroll(b, ssz); + bscroll(b, s - ssz); + return; + } + if (s < -ssz) { + bscroll(b, -ssz); + bscroll(b, s + ssz); + return; + } + + b->scroll.above += s; + b->scroll.above = CLAMP(b->scroll.above, 0, b->scroll.size); + + if (s > 0) { + if (b->scroll.size) { + for (i = 0; i < s; i++) { + tmp = b->scroll.top[i]; + b->scroll.top[i] = b->scroll.buf[b->scroll.index]; + b->scroll.buf[b->scroll.index] = tmp; + + b->scroll.index++; + if (b->scroll.index == b->scroll.size) + b->scroll.index = 0; + } + } else + for (i = 0; i < s; i++) + zero(b->scroll.top+i, 0, b->maxw); + } + + roll(b->scroll.top, b->scroll.bot, s); + + if (s < 0) { + if (b->scroll.size) { + for (i = (-s) - 1; i >= 0; i--) { + b->scroll.index--; + if (b->scroll.index == -1) + b->scroll.index = b->scroll.size - 1; + + tmp = b->scroll.top[i]; + + b->scroll.top[i] = b->scroll.buf[b->scroll.index]; + b->scroll.buf[b->scroll.index] = tmp; + b->scroll.top[i].dirty = 1; + } + } else + for (i = (-s) - 1; i >= 0; i--) + zero(b->scroll.top+i, 0, b->maxw); + } +} + +static +void +bresize(Buffer *b, int nrow, int ncol) +{ + int r, d; + Row *row = b->row; + Row *cur = row + b->cur.row; + + if (b->h != nrow) { + /* scroll if we can */ + if (cur >= row + nrow) + bscroll(b, b->cur.row - nrow + 1); + while (b->h > nrow) { + free(row[b->h - 1].cells); + b->h--; + } + + row = realloc(row, sizeof(Row) * nrow); + } + + if (b->maxw < ncol) { + /* expand each row */ + for (r = 0; r < b->h; r++) { + row[r].cells = realloc(row[r].cells, sizeof(Cell) * ncol); + if (b->h < ncol) + zero(row + r, b->w, ncol - b->w); + row[r].dirty = 1; + } + /* expand the scroll buffer */ + Row *sbuf = b->scroll.buf; + for (r = 0; r < b->scroll.size; r++) { + sbuf[r].cells = realloc(sbuf[r].cells, sizeof(Cell) * ncol); + if (b->w < ncol) + zero(sbuf + r, b->w, ncol - b->w); + } + b->maxw = b->w = ncol; + } else if (b->w != ncol) { + for (r = 0; r < b->h; r++) + row[r].dirty = 1; + b->w = ncol; + } + + d = 0; + if (b->h < nrow) { + while (b->h < nrow) { + row[b->h].cells = calloc(b->maxw, sizeof(Cell)); + zero(row + b->h, 0, b->maxw); + b->h++; + } + + /* prepare for backfill */ + if (cur >= b->scroll.bot - 1) { + d = b->row + nrow - cur - 1; + if (d > b->scroll.above) + d = b->scroll.above; + } } - w->buf = w->buffer; - return w; + + b->cur.row += row - b->row; + b->scroll.top = row; + b->scroll.bot = row + nrow; + b->row = row; + + /* perform backfill */ + if (d > 0) { + bscroll(b, -d); + b->cur.row += d; + } +} + +static +bool +binit(Buffer *b, int cols, int rows, int scroll) +{ + int size; + + b->pen.state = PenNormal; + b->pen.col.fg = b->pen.col.bg = -1; + + size = MAX(scroll, 0); + if (size && !(b->scroll.buf = calloc(size, sizeof(Row)))) + return false; + + b->scroll.size = size; + bresize(b, rows, cols); + + b->cur = (Dot){0}; + b->save = b->cur; + + return true; +} + +static +void +bboundary(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) +{ + if (bs) + *bs = nil; + if (be) + *be = nil; + if (as) + *as = nil; + if (ae) + *ae = nil; + if (!b->scroll.size) + return; + + if (b->scroll.above) { + if (bs) + *bs = &b->scroll.buf[(b->scroll.index - b->scroll.above + b->scroll.size) % b->scroll.size]; + if (be) + *be = &b->scroll.buf[(b->scroll.index-1 + b->scroll.size) % b->scroll.size]; + } + if (b->scroll.below) { + if (as) + *as = &b->scroll.buf[b->scroll.index]; + if (ae) + *ae = &b->scroll.buf[(b->scroll.index + b->scroll.below-1) % b->scroll.size]; + } +} + +static +Row * +browfirst(Buffer *b) +{ + Row *bstart; + if (!b->scroll.size || !b->scroll.above) + return b->row; + bboundary(b, &bstart, nil, nil, nil); + return bstart; +} + +static +Row * +browlast(Buffer *b) +{ + Row *aend; + if (!b->scroll.size || !b->scroll.below) + return b->row + b->h - 1; + bboundary(b, nil, nil, nil, &aend); + return aend; +} + +static +Row * +brownext(Buffer *b, Row *row) +{ + Row *before_start, *before_end, *after_start, *after_end; + Row *first = b->row, *last = b->row + b->h - 1; + + if (!row) + return nil; + + bboundary(b, &before_start, &before_end, &after_start, &after_end); + + if (row >= first && row < last) + return ++row; + if (row == last) + return after_start; + if (row == before_end) + return first; + if (row == after_end) + return nil; + if (row == &b->scroll.buf[b->scroll.size - 1]) + return b->scroll.buf; + return ++row; +} + +static +Row * +bprevrow(Buffer *b, Row *row) +{ + Row *before_start, *before_end, *after_start, *after_end; + Row *first = b->row, *last = b->row + b->h - 1; + + if (!row) + return nil; + + bboundary(b, &before_start, &before_end, &after_start, &after_end); + + if (row > first && row <= last) + return --row; + if (row == first) + return before_end; + if (row == before_start) + return nil; + if (row == after_start) + return last; + if (row == b->scroll.buf) + return &b->scroll.buf[b->scroll.size - 1]; + return --row; +} + +// ----------------------------------------------------------------------- +// windows + +Window * +wmake(Window *root, int top, int left, int w, int h, int scroll) +{ + Window *child, *it; + + child = calloc(1, sizeof(*child)); + child->top = top; + child->left = left; + child->parent = root; + if (root) { + if (root->child) { + for (it = root->child; it->link != nil; it = it->link) + ; + it->link = child; + } else + root->child = child; + + child->curvis = root->curvis; + child->blink = root->blink; + } + + if (!binit((Buffer*)child, w, h, scroll)) { + free(child); + return nil; + } + + return child; +} + +void +wfree(Window *win) +{ + free(win); +} + +void +wresize(Window *win, int w, int h) +{ + bresize((Buffer*)win, w, h); +} + +/* TODO: more sophisticated damage tracking */ +void +wputrune(Window *win, rune r) +{ + Row *row = win->row + win->cur.row; + Cell *cell = row->cells + win->cur.col; + + cell->pen = win->pen; + cell->txt = r; + + if (win->cur.col++ >= win->w) { + win->cur.col = 0; + if (win->cur.row++ >= win->h) + win->cur.row = win->h-1; + } + row->dirty = 1; +} + +void +wscroll(Window *win, int s) +{ + bscroll((Buffer*)win, s); } -- cgit v1.2.1