From ce05175372a9ddca1a225db0765ace1127a39293 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 12 Nov 2021 09:22:01 -0800 Subject: chore: simplified organizational structure --- src/libterm/term.c | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/libterm/term.h | 270 ++++++++++++++++++++++++++++ src/libterm/window.c | 408 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1167 insertions(+) create mode 100644 src/libterm/term.c create mode 100644 src/libterm/term.h create mode 100644 src/libterm/window.c (limited to 'src/libterm') diff --git a/src/libterm/term.c b/src/libterm/term.c new file mode 100644 index 0000000..11591fc --- /dev/null +++ b/src/libterm/term.c @@ -0,0 +1,489 @@ +#include "term.h" + +#include +#include + +struct ExtraInfo +{ + char *enteralt; + char *exitalt; + + char *entermouse; + char *exitmouse; +}; + +static +struct ExtraInfo vt200 = +{ + .enteralt = "\e[?1049h", + .exitalt = "\e[?1049l", + + .entermouse = "\e[?1049h\e[?1006l", + .exitmouse = "\e[?1002l\e[?1006l", +}; + +static Term *sigwinchhead; + +// ----------------------------------------------------------------------- +// database lookup + +static +char* +tryinfostr(Term *t, enum unibi_string s) +{ + char *val = (char*)unibi_get_str(t->info, s); + /* TODO: provide fallbacks */ + return val; +} + +static +char* +guessinfostr(Term *t, enum unibi_string s, char *guess) +{ + char *val = (char*)unibi_get_str(t->info, s); + if (!val) + return guess; + return val; +} + +static +char* +getinfostr(Term *t, enum unibi_string s) +{ + char *val = tryinfostr(t, s); + if (!val) + panicf("required term info string '%s' missing", unibi_name_str(s)); + + return val; +} + +static +char * +tryextrastr(Term *t, char *name) +{ + const char *nm; + size_t max = unibi_count_ext_str(t->info); + for (size_t i = 0; i < max; i++) { + nm = unibi_get_ext_str_name(t->info, i); + if (nm && !strcmp(nm, name)) { + return (char *)nm; + } + } + return nil; +} + +static +char * +guessextrastr(Term *t, char *name, char *guess) +{ + char *s; + if ((s = tryextrastr(t, name))) + return s; + + return guess; +} + +/* formats escape strings and writes to output */ +static void tfmt(Term *t, char *esc, int n, ...); +static void tclear(Term *t); + +// ----------------------------------------------------------------------- +// exported term methods + +static +char * +ttmpbuf(Term *t, int len) +{ + if (t->tmp.len >= len) + return t->tmp.b; + + /* TODO: error handling */ + return (t->tmp.b = realloc(t->tmp.b, len)); +} + +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; + + t->cap.colors = unibi_get_num(t->info, unibi_max_colors); + t->cap.bce = unibi_get_bool(t->info, unibi_back_color_erase); + + /* initialize root window (get current size)*/ + struct winsize ws = { 0 }; + if (ioctl(t->fd, TIOCGWINSZ, &ws) == 1) + goto bad; + + 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; + + /* get all term info format strings */ + t->esc.cup = getinfostr(t, unibi_cursor_address); + t->esc.vpa = tryinfostr(t, unibi_row_address); + t->esc.hpa = tryinfostr(t, unibi_column_address); + t->esc.cuu = getinfostr(t, unibi_parm_up_cursor); + t->esc.cuu1 = tryinfostr(t, unibi_cursor_up); + t->esc.cud = getinfostr(t, unibi_parm_down_cursor); + t->esc.cud1 = tryinfostr(t, unibi_cursor_down); + t->esc.cuf = getinfostr(t, unibi_parm_right_cursor); + t->esc.cuf1 = tryinfostr(t, unibi_cursor_right); + t->esc.cub = getinfostr(t, unibi_parm_left_cursor); + t->esc.cub1 = tryinfostr(t, unibi_cursor_left); + t->esc.ich = getinfostr(t, unibi_parm_ich); + t->esc.ich1 = tryinfostr(t, unibi_insert_character); + t->esc.dch = getinfostr(t, unibi_parm_dch); + t->esc.dch1 = tryinfostr(t, unibi_delete_character); + t->esc.il = getinfostr(t, unibi_parm_insert_line); + t->esc.il1 = tryinfostr(t, unibi_insert_line); + t->esc.dl = getinfostr(t, unibi_parm_delete_line); + t->esc.dl1 = tryinfostr(t, unibi_delete_line); + t->esc.ech = getinfostr(t, unibi_erase_chars); + t->esc.ed2 = getinfostr(t, unibi_clear_screen); + t->esc.stbm = getinfostr(t, unibi_change_scroll_region); + t->esc.sgr = getinfostr(t, unibi_set_attributes); + t->esc.sgr0 = getinfostr(t, unibi_exit_attribute_mode); + t->esc.sgr_i0 = tryinfostr(t, unibi_exit_italics_mode); + t->esc.sgr_i1 = tryinfostr(t, unibi_enter_italics_mode); + t->esc.sgr_fg = getinfostr(t, unibi_set_a_foreground); + t->esc.sgr_bg = getinfostr(t, unibi_set_a_background); + t->esc.sm_csr = getinfostr(t, unibi_cursor_normal); + t->esc.rm_csr = getinfostr(t, unibi_cursor_invisible); + + /* extensions to terminfo */ + 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"); + + return t; + +bad: + panicf("failed to initialize terminal instance"); + free(t); + return nil; +} + +void +tfree(Term *t) +{ + if (t->mode.mouse) + twrite(t, 0, vt200.exitmouse); + if (!t->mode.cursorvis) + tfmt(t, t->esc.rm_csr, 0); + if (t->mode.altscreen) + 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 +tflush(Term *t) +{ + if (t->fd != -1) + write(t->fd, t->buf.b, t->buf.c - t->buf.b); + + t->buf.c = t->buf.b; +} + +void +twrite(Term *t, long len, char *s) +{ + int n; + if (!len) + len = strlen(s); + +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; + } +} + +void +tsetpen(Term *t, Pen new) +{ + int c; + ushort ic, in; + Pen cur = t->pen; + if (!memcmp(&new, &cur, sizeof(new))) + return; + + /* attributes */ + tfmt(t, t->esc.sgr, 9, + 0, /* standout */ + new.state & PenUnderline, + new.state & PenReverse, + new.state & PenBlink, + new.state & PenDim, + new.state & PenBold, + new.state & PenInvis, + 0, /* protect */ + 0); /* alt */ + + ic = cur.state & PenItalic; + in = new.state & PenItalic; + if (ic & ~in) + tfmt(t, t->esc.sgr_i0, 0); + else if (~ic & in) + tfmt(t, t->esc.sgr_i1, 0); + + /* fg/bg color */ + /* TODO: add a check for if the terminal supports true color */ + /* 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 { + tfmt(t, t->esc.sgr_fg, 1, new.col.fg); + tfmt(t, t->esc.sgr_bg, 1, new.col.bg); + } + + t->pen = new; +} + +static +void +tfmt(Term *t, char *esc, int n, ...) +{ + int i; + long len; + va_list args; + unibi_var_t param[9]; + char buf[64], *c = buf; + + if (!esc) + panicf("no terminfo escape string given"); + + va_start(args, n); + 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)) { + c = ttmpbuf(t, len); + unibi_run(esc, param, c, len); + } + + twrite(t, len, c); +} + +/* absolute move */ +static +int +tgoto(Term *t, int row, int col) +{ + if (row != -1 && col != -1) + tfmt(t, t->esc.cup, 2, row, col); + else if (row != -1) { + if (!t->esc.vpa) + return 0; + tfmt(t, t->esc.vpa, 1, row); + } else if (col != -1) { + if (col == 0) { + twrite(t, 1, "\r"); + return 1; + } + if (t->esc.hpa) + tfmt(t, t->esc.hpa, 1, col); + else if (t->esc.cuf) { + twrite(t, 1, "\r"); + tfmt(t, t->esc.cuf, 1, col); + } else + return 0; + } else + return 0; /* unreachable */ + + return 1; +} + +/* relative move */ +static +void +tjump(Term *t, int down, int right) +{ + if (down == 1 && t->esc.cud1) + tfmt(t, t->esc.cud1, 0); + else if (down == -1 && t->esc.cuu1) + tfmt(t, t->esc.cuu1, 0); + else if (down > 0) + tfmt(t, t->esc.cud, 1, down); + else if (down < 0) + tfmt(t, t->esc.cuu, 1, -down); + + if (right == 1 && t->esc.cuf1) + tfmt(t, t->esc.cuf1, 0); + else if (right == -1 && t->esc.cub1) + tfmt (t, t->esc.cub1, 0); + else if (right > 0) + tfmt(t, t->esc.cuf, 1, right); + else if( right < 0) + tfmt(t, t->esc.cub, 1, -right); +} + +static +void +tclear(Term *t) +{ + tfmt(t, t->esc.ed2, 0); +} + +void +tblit(Term *t, Window *win) +{ + 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 + +int +main() +{ + 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/src/libterm/term.h b/src/libterm/term.h new file mode 100644 index 0000000..6bd2f6b --- /dev/null +++ b/src/libterm/term.h @@ -0,0 +1,270 @@ +#pragma once + +#include +#include + +#include +#include + +#define iota(x) 1 << (x) + +typedef struct RGB8 RGB8; +typedef struct Pen Pen; + +typedef struct Dot Dot; +typedef struct Cell Cell; +typedef struct Row Row; +typedef struct Buffer Buffer; +typedef struct Window Window; + +typedef struct Node Node; +typedef struct Key Key; +typedef struct Input Input; + +typedef struct Term Term; + +struct RGB8 +{ + uint8 r, g, b; +}; + +enum +{ + PenNormal = 0, + PenBold = iota(0), + PenDim = iota(1), + PenInvis = iota(2), + PenItalic = iota(3), + PenReverse = iota(4), + PenStrike = iota(5), + PenUnderline = iota(6), + PenBlink = iota(7), + /* ... */ + PenRGB = iota(15), +}; + +struct Pen +{ + ushort state; + union { + /* 256 color (legacy) */ + struct { + sshort fg : 8, bg : 8; /* 0 - 255 or COLOUR_DEFAULT */ + } col; + /* true color (modern) */ + struct { + RGB8 fg, bg; + } rgb; + }; +}; + +/* outputs */ +struct Cell +{ + rune txt; + Pen pen; +}; + +struct Row +{ + Cell *cells; + uint dirty : 1; +}; + +struct Dot +{ + int row, col; +}; + +/* + * scroll.top & scroll.bot are pointers into the viewport. + * + * scroll back buffer + * + * scroll.buf->+----------------+-----+ + * | | | ^ \ + * | before | | | | + * current terminal content | viewport | | | | + * | | | | + * +----------------+-----+\ | | | s > scroll.above + * ^ | | i | \ | | i | c | + * | | | n | \ | | n | r | + * | | v | \ | | v | o | + * | | 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 + * <- maxw -> | scroll back | | + * <- w -> | buffer | | | + * | | | | + * | | | v + * scroll.buf + scroll.size->+----------------+-----+ + * <- maxw -> + * <- w -> + */ + +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; + Row *top; + Row *bot; + int size; + int index; + int above; + int below; + } scroll; + 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 */ +struct Key +{ + int type; + int mods; + uchar utf8[UTFmax+1]; + union { + rune pt; + int num; + int sym; + char mouse[4]; + } code; +}; + +struct KeyInfo +{ + int type; + int sym; + int modmask; + int modset; +}; + +struct Input +{ + int fd; + int flags; + 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]; + } rbuf; + struct { + uchar *s, bytes[256]; + } ebuf; + + /* key data */ + Node *keys; + struct KeyInfo c0[32]; +}; + + +struct Term +{ + /* meta data */ + char *name; + unibi_term *info; + struct { + uchar altscreen : 1; + uchar cursorvis : 1; + uchar mouse : 1; + } mode; + struct { + uchar bce : 1; + int colors; + } cap; + + /* input capture */ + Input input; + + /* output display */ + Window *root; + Pen pen; + + /* raw text to pty */ + int fd; + struct { + char *c, b[512]; + } buf; + + struct { + int len; + char *b; + } tmp; + + /* info */ + struct { + /* Positioning */ + char *cup; // cursor_address + char *vpa; // row_address == vertical position absolute + char *hpa; // column_address = horizontal position absolute + + /* Moving */ + char *cuu; char *cuu1; // Cursor Up + char *cud; char *cud1; // Cursor Down + char *cuf; char *cuf1; // Cursor Forward == Right + char *cub; char *cub1; // Cursor Backward == Left + + /* Editing */ + char *ich; char *ich1; // Insert Character + char *dch; char *dch1; // Delete Character + char *il; char *il1; // Insert Line + char *dl; char *dl1; // Delete Line + char *ech; // Erase Character + char *ed2; // Erase Data 2 == Clear screen + char *stbm; // Set Top/Bottom Margins + + /* formatting */ + char *sgr; // Select Graphic Rendition + char *sgr0; // Exit Attribute Mode + char *sgr_i0, *sgr_i1; // SGR italic off/on + char *sgr_fg; // SGR foreground colour + char *sgr_bg; // SGR background colour + + /* Mode setting/clearing */ + char *sm_csr; char *rm_csr; // Set/reset mode: Cursor visible + + /* augmentations to terminfo */ + struct { + char *rgbf; // rgb foreground + char *rgbb; // rgb background + char *smxx; // strikethrough + char *smulx; // curly underline + } ext; + } esc; + + Term *link; +}; + +/* functions */ +void tresize(Term *t); + +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/src/libterm/window.c b/src/libterm/window.c new file mode 100644 index 0000000..5d36c8b --- /dev/null +++ b/src/libterm/window.c @@ -0,0 +1,408 @@ +#include "term.h" + +// ----------------------------------------------------------------------- +// buffers + +static +void +zero(Row *row, int start, int len) +{ + 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; +} + +static +void +roll(Row *start, Row *end, int count) +{ + int n = end - start; + + /* enforce circularity */ + count %= n; + if (count < 0) + count += n; + + 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) +{ + 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; + } + } +} + +static +void +bfini(Buffer *b) +{ + int i; + + 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; + } + } + + 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