summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.c163
-rw-r--r--txt.c92
-rw-r--r--txt.h20
-rw-r--r--utf8.h89
-rw-r--r--vui.c836
-rw-r--r--vui.h144
6 files changed, 1318 insertions, 26 deletions
diff --git a/main.c b/main.c
index e367825..3862625 100644
--- a/main.c
+++ b/main.c
@@ -1,27 +1,174 @@
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
+#include <ctype.h>
+#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#define ARENA_IMPL
+#define UTF8_IMPL
#include "wrmr.h"
#include "arena.h"
#include "txt.h"
+#include "vui.h"
+#include "utf8.h"
-int main(void) {
- Arena a = arena_init(1L << 20);
- Txt txt = { 0 };
- txt_open(&txt, "test.txt");
- txt_insert(&txt, txt.len, "wheeee", 6);
- txt_delete(&txt, txt.len, 6);
+Arena scratch = { 0 };
+Txt txt = { 0 };
+u32 cur = 0;
+
+int is_space(u32 c) {
+ return c <= 0x20 && c != 0;
+}
+
+u32 move_word_back(Txt *t, u32 cur) {
+ TxtLoc l = txt_at(t, cur);
+ while (!txt_at_start(t,l)) {
+ TxtLoc n = txt_prev(t, l);
+ if (!is_space(txt_chr(t, n))) break;
+ l = n;
+ }
+ while (!txt_at_start(t,l)) {
+ TxtLoc n = txt_prev(t, l);
+ if (is_space(txt_chr(t, n))) break;
+ l = n;
+ }
+ return txt_ofs(t, l);
+}
+
+u32 move_word_fwd(Txt *t, u32 cur) {
+ TxtLoc l = txt_at(t, cur);
+ while (!txt_at_end(t,l) && is_space(txt_chr(t, l))) l = txt_next(t, l);
+ while (!txt_at_end(t,l) && !is_space(txt_chr(t, l))) l = txt_next(t, l);
+ return txt_ofs(t, l);
+}
+
+u32 move_char_back(Txt *t, u32 cur) {
+ while (cur > 0) {
+ cur--;
+ u8 c = txt_byte(t, txt_at(t, cur));
+ if ((c & 0xc0) != 0x80) break;
+ }
+ return cur;
+}
+
+u32 move_char_fwd(Txt *t, u32 cur) {
+ u8 b = txt_byte(t, txt_at(t, cur));
+ u32 n = stdc_leading_ones(b);
+ if (cur + n >= t->len) return t->len;
+ return cur + n + !n;
+}
+
+u32 del_between(Txt *t, u32 a, u32 b) {
+ if (b < a) { u32 t = a; a = b; b = t; }
+ //if (b - a > 10) TRAP();
+ return txt_delete(t, b, b - a);
+}
+
+/* main */
+
+#define ODD_ATTR (FG_CYAN | BG_BLACK)
+#define EVEN_ATTR (FG_WHITE | BG_BLACK)
+
+void draw(void *ctx) {
+ (void)ctx;
+redraw:;
+ vui_clear();
+ static int max_y = 0;
+ int x = 0, y = 0;
+ u32 ofs = 0;
for (u32 i = 0; i < txt.ptbl.n; i++) {
+ Arena stk = scratch;
TxtPiece *p = &txt.ptbl.v[i];
- printf("%u. %.*s\n", i, (int)p->n, txt.buf[p->buf].s + p->ofs);
+ const char *s = txt.buf[p->buf].s + p->ofs;
+
+ u32 bufn = utf8_decode_len(s, p->n);
+ u32 *buf = new_arr(&scratch, u32, bufn);
+ utf8_decode(buf, s, bufn);
+
+ VuiAttr a = i&1 ? ODD_ATTR : EVEN_ATTR;
+ vui_aprintf(-1, i, a, "%u, %u (%s)", p->ofs, p->n, p->buf == TXT_ADD ? "add" : "src");
+
+ for (u32 j = 0; j < bufn; j++) {
+ u32 c = buf[j];
+ if (ofs == cur) vui_curs_pos(x, y - (max_y - LINES + 1));
+ if (c != '\r' && c != '\n') {
+ vui_chra(x++, y - (max_y - LINES + 1), c, a);
+ }
+ if (c == '\n') {
+ x = 0;
+ y++;
+ }
+ ofs += UTF8_CP_LEN(c);
+ }
+ scratch = stk;
+ }
+ if (ofs == cur) vui_curs_pos(x, y - (max_y - LINES + 1));
+ if (y > max_y) {
+ max_y = y;
+ goto redraw;
+ }
+ TxtLoc l = txt_at(&txt, cur);
+ u32 c = txt_chr(&txt, l);
+ vui_printf(-1, -1, "%u, %u - %02x (%c)", l.p, l.i, c, (c < 0x20 || c > 0x7e) ? ' ' : c);
+ u32 used = scratch.beg - scratch.start, max = scratch.end - scratch.start;
+ vui_printf(-1, -2, "scratch %.02f/%.02fk", used/1024.0, max/1024.0);
+}
+
+int main(void) {
+ scratch = arena_init(1L << 20);
+ vui_init();
+ vui_curs_vis(1);
+ vui_redraw_fn(draw);
+ if (txt_load(&txt, "test.txt")) err(1, "couldn't open file");
+ cur = txt.len;
+ for (;;) {
+ scratch.beg = scratch.start;
+ draw(NULL);
+ vui_blit();
+ u32 c = vui_key();
+ switch (c) {
+ case KEY_ESC:
+ goto brk;
+ case KEY_BKSP:
+ cur = txt_delete_c(&txt, cur);
+ break;
+ case KEY_LEFT:
+ cur = move_char_back(&txt, cur);
+ break;
+ case KEY_LEFT | KEY_CTRL_BIT:
+ cur = move_word_back(&txt, cur);
+ break;
+ case KEY_RIGHT:
+ cur = move_char_fwd(&txt, cur);
+ break;
+ case KEY_RIGHT | KEY_CTRL_BIT:
+ cur = move_word_fwd(&txt, cur);
+ break;
+ case 0x17 /* ^W */:
+ cur = del_between(&txt, move_word_back(&txt, cur), cur);
+ break;
+ case 0x05 /* ^E */:
+ cur = del_between(&txt, cur, move_word_fwd(&txt, cur));
+ break;
+ case 0x13 /* ^S */:
+ txt_save(&txt, "test.txt");
+ txt_load(&txt, "test.txt");
+ break;
+ case '\r':
+ cur = txt_insert_c(&txt, cur, '\n');
+ break;
+ default:
+ if (c >= ' ' && c <= KEY_UTF8_MAX) cur = txt_insert_c(&txt, cur, c);
+ break;
+ }
}
- arena_free(&a);
+brk:
+ vui_fini();
+ arena_free(&scratch);
return 0;
}
diff --git a/txt.c b/txt.c
index 9f29f84..8fcafe9 100644
--- a/txt.c
+++ b/txt.c
@@ -9,6 +9,7 @@
#include "wrmr.h"
#include "dynarr.h"
#include "txt.h"
+#include "utf8.h"
void txt_replace_piece(Txt *b, u32 pi, TxtBufIdx buf, u32 ofs, u32 n) {
b->ptbl.v[pi] = (TxtPiece) { buf, ofs, n };
@@ -39,16 +40,6 @@ u32 txt_split_piece(Txt *b, u32 pi, u32 i) {
return pi + 1;
}
-TxtLoc txt_at(Txt *b, u32 cur) {
- for (u32 i = 0; i < b->ptbl.n; i++) {
- if (cur < b->ptbl.v[i].n) {
- return (TxtLoc) { i, cur };
- }
- cur -= b->ptbl.v[i].n;
- }
- return (TxtLoc) { b->ptbl.n - 1, b->ptbl.v[b->ptbl.n - 1].n };
-}
-
static void txt_buf_fit(Txt *b, TxtBufIdx bi, u32 sz) {
TxtBuf *buf = &b->buf[bi];
if (sz > buf->c) {
@@ -98,9 +89,11 @@ u32 txt_insert(Txt *b, u32 cur, const char *s, u32 n) {
return cur + n;
}
-u32 txt_insert_c(Txt *b, u32 cur, char ch) {
- /* TODO: utf-8 char */
- return txt_insert(b, cur, &ch, 1);
+u32 txt_insert_c(Txt *b, u32 cur, u32 ch) {
+ char buf[6];
+ u32 n = utf8_encode_len(&ch, 1);
+ utf8_encode(buf, &ch, 1);
+ return txt_insert(b, cur, buf, n);
}
static int txt_are_pieces_adjacent(Txt *t, u32 a, u32 b) {
@@ -144,6 +137,13 @@ u32 txt_delete(Txt *b, u32 cur, u32 n) {
return cur;
}
+u32 txt_delete_c(Txt *b, u32 cur) {
+ while (cur > 0 && (txt_byte(b, txt_at(b, cur-1)) & 0xc0) == 0x80)
+ cur = txt_delete(b, cur, 1);
+ cur = txt_delete(b, cur, 1);
+ return cur;
+}
+
int txt_load(Txt *b, const char *path) {
struct stat sb;
int fd = open(path, O_RDONLY);
@@ -192,3 +192,69 @@ void txt_free(Txt *t) {
}
free(t->ptbl.v);
}
+
+/* reading individual chars */
+
+u8 txt_byte(Txt *t, TxtLoc l) {
+ TxtPiece *p = &t->ptbl.v[l.p];
+ TxtBuf *b = &t->buf[p->buf];
+ if (l.p < t->ptbl.n && l.i == p->n) p++, l.i = 0;
+ if (p->ofs + l.i >= b->n) return 0;
+ return b->s[p->ofs + l.i];
+}
+
+u32 txt_chr(Txt *t, TxtLoc l) {
+ TxtPiece *p = &t->ptbl.v[l.p];
+ TxtBuf *b = &t->buf[p->buf];
+ if (l.p < t->ptbl.n && l.i == p->n) p++, l.i = 0;
+ return utf8_decode_at(b->s, p->ofs + l.i, b->n);
+}
+
+/* cursor manipulation */
+
+TxtLoc txt_at(Txt *b, u32 cur) {
+ for (u32 i = 0; i < b->ptbl.n; i++) {
+ if (cur < b->ptbl.v[i].n) {
+ return (TxtLoc) { i, cur };
+ }
+ cur -= b->ptbl.v[i].n;
+ }
+ return (TxtLoc) { b->ptbl.n - 1, b->ptbl.v[b->ptbl.n - 1].n };
+}
+
+u32 txt_ofs(Txt *b, TxtLoc l) {
+ u32 r = 0;
+ for (u32 i = 0; i < l.p; i++) {
+ r += b->ptbl.v[i].n;
+ }
+ return r + l.i;
+}
+
+TxtLoc txt_next(Txt *b, TxtLoc l) {
+ TxtPiece *p = &b->ptbl.v[l.p];
+ if (l.i+1 < p->n) return (TxtLoc) { l.p, l.i + 1 };
+ if (l.p+1 < b->ptbl.n) {
+ return (TxtLoc) { l.p + 1, l.i };
+ } else {
+ return (TxtLoc) { l.p, p->n };
+ }
+}
+
+TxtLoc txt_prev(Txt *b, TxtLoc l) {
+ if (l.i > 0) {
+ return (TxtLoc) { l.p, l.i - 1 };
+ } else if (l.p > 0) {
+ return (TxtLoc) { l.p - 1, b->ptbl.v[l.p - 1].n - 1 };
+ } else {
+ return (TxtLoc) { 0, 0 };
+ }
+}
+
+int txt_at_start(Txt *b, TxtLoc l) {
+ (void)b;
+ return l.p == 0 && l.i == 0;
+}
+
+int txt_at_end(Txt *b, TxtLoc l) {
+ return l.p + 1 == b->ptbl.n && l.i == b->ptbl.v[l.p].n;
+}
diff --git a/txt.h b/txt.h
index 21712af..54b2146 100644
--- a/txt.h
+++ b/txt.h
@@ -28,16 +28,26 @@ typedef struct {
u32 len;
} Txt;
-TxtLoc txt_at(Txt *b, u32 cur);
-
u32 txt_split_piece(Txt *b, u32 pi, u32 i);
void txt_remove_piece(Txt *b, u32 pi);
void txt_insert_piece(Txt *b, u32 pi, TxtBufIdx buf, u32 ofs, u32 n);
-void txt_buf_append(Txt *b, TxtBufIdx bi, const char *s, u32 n);
u32 txt_insert(Txt *b, u32 cur, const char *s, u32 n);
-u32 txt_insert_c(Txt *b, u32 cur, char ch);
u32 txt_delete(Txt *b, u32 cur, u32 n);
-int txt_open(Txt *b, const char *path);
+u32 txt_insert_c(Txt *b, u32 cur, u32 ch);
+u32 txt_delete_c(Txt *b, u32 cur);
+int txt_load(Txt *b, const char *path);
+int txt_save(Txt *b, const char *path);
+void txt_free(Txt *b);
+
+TxtLoc txt_at(Txt *b, u32 cur);
+u32 txt_ofs(Txt *b, TxtLoc l);
+TxtLoc txt_next(Txt *b, TxtLoc l);
+TxtLoc txt_prev(Txt *b, TxtLoc l);
+int txt_at_start(Txt *b, TxtLoc l);
+int txt_at_end(Txt *b, TxtLoc l);
+
+u32 txt_chr(Txt *b, TxtLoc l);
+u8 txt_byte(Txt *b, TxtLoc l);
#endif
diff --git a/utf8.h b/utf8.h
new file mode 100644
index 0000000..01c3336
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,89 @@
+/* utf-8 encoding and decoding library */
+
+#ifndef UTF8_H
+#define UTF8_H
+
+#include "wrmr.h"
+
+#define UTF8_INVALID 0xFFFD /* replacement character */
+
+u32 utf8_decode_len(const char *src, u32 ch_count);
+u32 utf8_encode_len(const u32 *src, u32 cp_count);
+void utf8_decode(u32 *dst, const char *src, u32 cp_count);
+void utf8_encode(char *dst, const u32 *src, u32 cp_count);
+u32 utf8_decode_at(const char *s, u32 i, u32 n);
+
+#ifdef UTF8_IMPL
+
+#include <stdbit.h>
+
+/* packed array of 2-bit lengths for codepoints 0..10FFFF */
+#define UTF8_CP_LEN_BITS ((uint64_t)0xFFEAA550000)
+#define UTF8_CP_SHIFT(cp) ((32 - stdc_leading_zeros((uint32_t)(cp))) << 1)
+#define UTF8_CP_LEN(cp) (1 + ((UTF8_CP_LEN_BITS >> UTF8_CP_SHIFT(cp)) & 3))
+
+u32 utf8_encode_len(const u32 *src, u32 cp_count) {
+ u32 len = 0;
+ while (cp_count) len += UTF8_CP_LEN(src[--cp_count]);
+ return len;
+}
+
+u32 utf8_decode_len(const char *src, u32 ch_count) {
+ u32 i = 0, len = 0;
+ while (i < ch_count) {
+ i += stdc_leading_ones((u8)src[i]) + ((~src[i] & 0x80) >> 7);
+ len++;
+ }
+ return len;
+}
+
+void utf8_encode(char *dst, const u32 *src, u32 cp_count) {
+ while (cp_count--) {
+ u32 c = *src++;
+ ASSUME(c > 0 && c < 0x110000);
+ u32 len = UTF8_CP_LEN(c);
+ ASSUME(len > 0 && len < 5);
+ if (len > 1) {
+ for (u32 i = len; --i;) {
+ dst[i] = 0x80 | (c & 0x3f);
+ c >>= 6;
+ }
+ *dst = (0xf0 << (4 - len)) | c;
+ dst += len;
+ } else {
+ *dst++ = c;
+ }
+ }
+}
+
+void utf8_decode(u32 *dst, const char *src, u32 cp_count) {
+ while (cp_count--) {
+ u8 c = *src++;
+ u32 bits = stdc_leading_ones(c);
+ ASSUME(bits < 5);
+ u32 cp = c & (0xff >> bits);
+ while (bits-- > 1) {
+ c = *src++;
+ cp = (cp << 6) | (c & 0x3F);
+ }
+ *dst++ = cp;
+ }
+}
+
+u32 utf8_decode_at(const char *s, u32 i, u32 n) {
+ if (i >= n) return 0;
+ u32 cp = (u8)s[i++];
+ u32 b = stdc_leading_ones((u8)cp);
+ if (!b) return cp;
+ u32 end = i + b - 1;
+ if (end >= n) return 0;
+ cp &= 0xff >> b;
+ while (i < end) {
+ u8 c = s[i++];
+ cp = (cp << 6) | (c & 0x3f);
+ }
+ return cp;
+}
+
+#endif
+#endif
diff --git a/vui.c b/vui.c
new file mode 100644
index 0000000..cc8808c
--- /dev/null
+++ b/vui.c
@@ -0,0 +1,836 @@
+#define _POSIX_C_SOURCE 20250918
+
+#include <sys/ioctl.h>
+
+#include <stdlib.h>
+#include <stdbit.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <unistd.h>
+#include <termios.h>
+#include <signal.h>
+#include <poll.h>
+#include <time.h>
+
+#include "wrmr.h"
+#include "vui.h"
+#include "utf8.h"
+
+#define CSI "\x1b["
+
+#define VUI_LOG_OUTPUT 0
+#define VUI_LOG_OUTPUT_FILE "out_log.txt"
+
+/* terminal control */
+
+static void vui_getwinsz(unsigned *w, unsigned *h);
+static void vui_clrspan(VuiBuffer *buf, unsigned x0, unsigned x1, unsigned y);
+static void vui_clrtoeol(VuiBuffer *buf, unsigned x0, unsigned y);
+
+static void attr_chg(VuiAttr *ptr, VuiAttr to);
+static inline void curs_move(unsigned *restrict ptr_x, unsigned *restrict ptr_y, unsigned dst_x, unsigned dst_y);
+
+static void on_resized(int _);
+static void adjust_win_size(void);
+static void resize_win(unsigned nw, unsigned nh);
+
+/* buffer handling */
+
+static void clear_buf(VuiBuffer *buf);
+static void resize_buf(VuiBuffer *buf, unsigned nw, unsigned nh);
+static void free_buf(VuiBuffer *buf);
+
+static inline int bchr_equiv(VuiBuffer *a, VuiBuffer *b, unsigned i);
+
+/* input */
+
+static unsigned getk(void);
+static unsigned getk_utf8(uint8_t start);
+static VuiKey bad_key(void);
+static VuiKey meta_key(u32 c);
+static VuiKey esc_key(u32 c);
+
+/* output */
+
+static void vui_out_fit(size_t n);
+static void vui_outf(const char *fmt, ...);
+static inline void vui_outc(char c);
+static inline void vui_outvc(VuiChar c);
+static inline void vui_outvcn(VuiChar *c, size_t n);
+static inline void vui_outsn(const char *s, unsigned n);
+static inline void vui_outs(const char *s);
+static inline void vui_out_flush(void);
+
+/* globals */
+
+static struct termios vui_init_stdin_tos, vui_init_stdout_tos, vui_raw_stdin_tos, vui_raw_stdout_tos;
+
+static char *vui_out = NULL;
+static size_t vui_outn = 0;
+static size_t vui_out_cap = 0;
+
+static VuiWindow win = {
+ .front = &win.buf1,
+ .back = &win.buf2,
+};
+
+/* TODO
+ *
+ * - clean up keyboard input
+ * - separate out library interface
+ * - maybe some custom colors
+ */
+
+void vui_redraw_fn(void (*fn)(void *ctx)) {
+ win.redraw_fn = fn;
+}
+
+void vui_redraw_ctx(void *ctx) {
+ win.redraw_ctx = ctx;
+}
+
+VuiWindow *vui_win(void) {
+ return &win;
+}
+
+static void vui_getwinsz(unsigned *w, unsigned *h) {
+ struct winsize wsz;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsz);
+ *w = wsz.ws_col;
+ *h = wsz.ws_row;
+}
+
+static void vui_clrspan(VuiBuffer *buf, unsigned x0, unsigned x1, unsigned y) {
+ for (unsigned x = x0; x < x1; x++) {
+ BCHR(buf, x, y) = ' ';
+ }
+ for (unsigned x = x0; x < x1; x++) {
+ BATTR(buf, x, y) = ATTR_DEFAULT;
+ }
+}
+
+static void vui_clrtoeol(VuiBuffer *buf, unsigned x0, unsigned y) {
+ vui_clrspan(buf, x0, buf->width, y);
+}
+
+static void clear_buf(VuiBuffer *buf) {
+ for (unsigned y = 0; y < buf->height; y++) {
+ vui_clrtoeol(buf, 0, y);
+ }
+}
+
+void vui_clear(void) {
+ clear_buf(win.front);
+}
+
+void vui_fill_rect(VuiChar c, VuiAttr a, int x0, int y0, int width, int height) {
+ if (x0 < 0) { width += x0; x0 = 0; }
+ if (y0 < 0) { height += y0; y0 = 0; }
+ if (x0 + width >= (int)COLS) width = (COLS - 1) - x0;
+ if (y0 + height >= (int)LINES) height = (LINES - 1) - y0;
+ if (width < 1 || height < 1) return;
+ for (int y = y0; y < y0 + height; y++) {
+ for (int x = x0; x < x0 + width; x++) {
+ CHR(x, y) = c;
+ ATTR(x, y) = a;
+ }
+ }
+}
+
+void vui_fill(VuiChar c, VuiAttr a) {
+ vui_fill_rect(c, a, 0, 0, COLS, LINES);
+}
+
+static void resize_buf(VuiBuffer *buf, unsigned nw, unsigned nh) {
+ VuiChar *nchr = calloc(nw * nh, sizeof(*buf->chr));
+ uint16_t *nattr = calloc(nw * nh, sizeof(*buf->attr));
+ assert(nchr);
+ assert(nattr);
+
+ unsigned oldw = buf->width;
+ unsigned oldh = buf->height;
+ unsigned minw = nw < oldw ? nw : oldw;
+ unsigned minh = nh < oldh ? nh : oldh;
+ for (unsigned y = 0; y < minh; y++) {
+ memcpy(nchr + y*nw, buf->chr + y*oldw, minw * sizeof(*buf->chr));
+ memcpy(nattr + y*nw, buf->attr + y*oldw, minw * sizeof(*buf->attr));
+ }
+
+ free(buf->chr);
+ free(buf->attr);
+ buf->chr = nchr;
+ buf->attr = nattr;
+ buf->width = nw;
+ buf->height = nh;
+ for (unsigned y = 0; y < minh; y++) vui_clrtoeol(buf, minw, y);
+ for (unsigned y = minh; y < buf->height; y++) vui_clrtoeol(buf, 0, y);
+}
+
+static void resize_win(unsigned nw, unsigned nh) {
+ resize_buf(&win.buf1, nw, nh);
+ resize_buf(&win.buf2, nw, nh);
+ win.width = nw;
+ win.height = nh;
+}
+
+static void adjust_win_size(void) {
+ unsigned w, h;
+ vui_getwinsz(&w, &h);
+ if (w != COLS || h != LINES) {
+ resize_win(w, h);
+ win.redraw_all = 1;
+ }
+}
+
+void vui_curs_vis(int vis) {
+ win.curs_vis = vis;
+ if (vis) {
+ fputs(CSI "?25h", stdout);
+ fflush(stdout);
+ } else {
+ fputs(CSI "?25l", stdout);
+ fflush(stdout);
+ }
+}
+
+void vui_curs_pos(int x, int y) {
+ win.curs_x = x;
+ win.curs_y = y;
+}
+
+/* TODO: use something better than signal() */
+/* does sigaction allow for a context pointer? */
+static void on_resized(int signo) {
+ (void)signo;
+ adjust_win_size();
+ if (win.redraw_fn) {
+ win.redraw_fn(win.redraw_ctx);
+ }
+ vui_blit();
+}
+
+void vui_init(void) {
+ tcgetattr(STDIN_FILENO, &vui_init_stdin_tos);
+ vui_raw_stdin_tos = vui_init_stdin_tos;
+ /*
+ vui_raw_stdin_tos.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
+ vui_raw_stdin_tos.c_lflag |= IGNBRK;
+ */
+
+ vui_raw_stdin_tos.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
+ //vui_raw_stdin_tos.c_oflag &= ~OPOST;
+ vui_raw_stdin_tos.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ vui_raw_stdin_tos.c_cflag &= ~(CSIZE | PARENB);
+ vui_raw_stdin_tos.c_cflag |= CS8;
+
+ tcsetattr(STDIN_FILENO, TCSANOW, &vui_raw_stdin_tos);
+
+ tcgetattr(STDOUT_FILENO, &vui_init_stdout_tos);
+ vui_raw_stdout_tos = vui_init_stdout_tos;
+ vui_raw_stdout_tos.c_oflag &= ~OPOST;
+ tcsetattr(STDOUT_FILENO, TCSANOW, &vui_raw_stdout_tos);
+
+ /* set white:black to default */
+ printf(CSI "40;37m" CSI "8]");
+ vui_curs_vis(0);
+ win.redraw_all = 1;
+
+ adjust_win_size();
+ sigaction(SIGWINCH, &(struct sigaction) { .sa_handler = on_resized }, NULL);
+}
+
+
+static void free_buf(VuiBuffer *buf) {
+ free(buf->chr);
+ free(buf->attr);
+ buf->chr = NULL;
+ buf->attr = NULL;
+ buf->width = 0;
+ buf->height = 0;
+}
+
+static inline int bchr_equiv(VuiBuffer *a, VuiBuffer *b, unsigned i) {
+ return a->chr[i] == b->chr[i] && a->attr[i] == b->attr[i];
+}
+
+void vui_fini(void) {
+ vui_curs_vis(1);
+ free_buf(&win.buf1);
+ free_buf(&win.buf2);
+ free(vui_out);
+ vui_out = NULL;
+ vui_out_cap = 0;
+ printf(CSI "0m" CSI "H" CSI "2J");
+ tcsetattr(STDIN_FILENO, TCSANOW, &vui_init_stdin_tos);
+ tcsetattr(STDOUT_FILENO, TCSANOW, &vui_init_stdout_tos);
+}
+
+static void vui_out_fit(size_t n) {
+ size_t c = stdc_bit_ceil(n);
+ if (c > vui_out_cap) {
+ char *p = realloc(vui_out, c);
+ if (!p) {
+ vui_fini();
+ fprintf(stderr, "failed to reallocate vui output buffer\n");
+ exit(1);
+ }
+ vui_out = p;
+ vui_out_cap = c;
+ }
+}
+
+static void vui_outf(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ int n = vsnprintf(NULL, 0, fmt, ap);
+ va_end(ap);
+ va_start(ap, fmt);
+ vui_out_fit(vui_outn + n);
+ vsprintf(&vui_out[vui_outn], fmt, ap);
+ vui_outn += n;
+ va_end(ap);
+}
+
+
+/* lookup table from utf-8 codepoint to byte length:
+ * { 6,6,6,6,6,6,5,5,5,5,5,4,4,4,4,4,3,3,3,3,3,2,2,2,2,1,1,1,1,1,1,1,1, }
+ * len(cp) = tbl[32 - clz(cp)].
+ *
+ * how could we possibly reduce this?
+ * 6 sixes, 5 fives, 5 fours, 5 threes, four twos, and eight ones.
+ * no obvious pattern.
+ *
+ * no utf8 elements above 0x10FFFF, so never more than 4 bytes:
+ * 4444433333222211111111
+ *
+ * subtract one from each, so they fit in 3 bytes:
+ * 3333322222111100000000
+ *
+ * pack into a u64:
+ * 0b11111111111010101010010101010000000000000000
+ *
+ * convert to hex, and now:
+ * len(cp) = 1 + (0xFFEAA550000 >> (2 * (32 - clz(cp)))) & 3
+ */
+
+static inline void vui_outc(char c) {
+ vui_out_fit(vui_outn + 1);
+ vui_out[vui_outn++] = c;
+}
+
+/* don't pass a NUL to this */
+/* it doesn't make sense to do so, and assuming non-zero lets us dodge a branch
+ * in stdc_leading_zeros() */
+static inline void vui_outvc(VuiChar c) {
+ vui_outvcn(&c, 1);
+}
+
+static inline void vui_outvcn(VuiChar *c, size_t n) {
+ u32 len = utf8_encode_len(c, n);
+ vui_out_fit(vui_outn + len);
+ utf8_encode(&vui_out[vui_outn], c, n);
+ vui_outn += len;
+}
+
+static inline void vui_outsn(const char *s, unsigned n) {
+ vui_out_fit(vui_outn + n);
+ memcpy(&vui_out[vui_outn], s, n);
+ vui_outn += n;
+}
+
+static inline void vui_outs(const char *s) {
+ vui_outsn(s, strlen(s));
+}
+
+static inline void vui_out_flush(void) {
+ /*fwrite(vui_out, 1, vui_outn, stdout);
+ fflush(stdout);*/
+
+ write(STDOUT_FILENO, vui_out, vui_outn);
+
+#if VUI_LOG_OUTPUT
+ static unsigned out_frame = 0;
+ FILE *f = fopen(VUI_LOG_OUTPUT_FILE, "a");
+ assert(f);
+ fprintf(f, "\n\n:: OUTPUT FRAME %u ::\n", ++out_frame);
+ fwrite(vui_out, 1, vui_outn, f);
+ fclose(f);
+#endif
+
+ vui_outn = 0;
+}
+
+static inline void curs_move(unsigned *restrict ptr_x, unsigned *restrict ptr_y, unsigned dst_x, unsigned dst_y) {
+ unsigned src_x = *ptr_x;
+ unsigned src_y = *ptr_y;
+ if (src_x != dst_x && src_y != dst_y) {
+ if (dst_x == src_x - 1) {
+ vui_outf(CSI "\b%ud", dst_y + 1);
+ } else if (dst_x > 0) {
+ vui_outf(CSI "%u;%uH", dst_y + 1, dst_x + 1);
+ } else if (dst_y > 0) {
+ vui_outf(CSI "%uH", dst_y + 1);
+ } else {
+ vui_outs(CSI "H");
+ }
+ } else if (src_x != dst_x) {
+ if (dst_x == 0) {
+ vui_outc('\r');
+ } else if (dst_x == src_x - 1) {
+ vui_outc('\b');
+ } else {
+ vui_outf(CSI "%uG", dst_x + 1);
+ }
+ } else if (src_y != dst_y) {
+ if (dst_y == src_y - 1 && dst_x == src_x + 1) {
+ vui_outs("\x1b" "M");
+ abort();
+ } else if (dst_y == src_y + 1 && dst_x == src_x + 1) {
+ vui_outs("\x1b" "D");
+ abort();
+ } else if (dst_y > 0) {
+ vui_outf(CSI "%ud", dst_y + 1);
+ } else if (dst_y == src_y - 1) {
+ vui_outs(CSI "A");
+ } else if (dst_y == src_y -+1) {
+ vui_outs(CSI "B");
+ } else {
+ vui_outs(CSI "d");
+ }
+ }
+ *ptr_x = dst_x;
+ *ptr_y = dst_y;
+}
+
+#define M(s) do {\
+ if (sep) vui_outc(';');\
+ vui_outs(s);\
+ sep = 1;\
+} while(0)
+
+static void attr_chg(VuiAttr *ptr, VuiAttr to) {
+ VuiAttr from = *ptr;
+ if (from == to) return;
+
+ /* bold colors will keep the terminal bold, even on changing after */
+ if ((from & 8) && (~to & 8) && (~to & A_BOLD)) {
+ from |= A_BOLD;
+ }
+
+ int attr_chg_count = stdc_count_ones(ATTR_A(from) ^ ATTR_A(to));
+ int chg_attr = (attr_chg_count != 0);
+ if (!chg_attr) goto chg_colors;
+
+ /* deduct color changes */
+ attr_chg_count -= (ATTR_FG(to) == ATTR_FG(from)) && (ATTR_FG(to) != ATTR_DEFAULT);
+ attr_chg_count -= (ATTR_BG(to) == ATTR_BG(from)) && (ATTR_BG(to) != ATTR_DEFAULT);
+
+ int should_rebuild = (attr_chg_count > 1) || to == ATTR_DEFAULT;
+
+ if (should_rebuild) {
+ vui_outs(CSI "0");
+ if (to & A_BOLD) vui_outs(";1");
+ if (to & A_DIM) vui_outs(";2");
+ if (to & A_ITALIC) vui_outs(";3");
+ if (to & A_UNDERSCORE) vui_outs(";4");
+ if (to & A_BLINK) vui_outs(";5");
+ if (to & A_REVERSE) vui_outs(";7");
+ from = ATTR_DEFAULT;
+ assert(ATTR_FG(from) == FG_WHITE);
+ assert(ATTR_BG(from) == BG_BLACK);
+ } else {
+ int sep = 0;
+ vui_outs(CSI);
+ if ((to ^ from) & A_BOLD) {
+ if (to & A_BOLD) M("1");
+ else {
+ M("22");
+ from &= (~A_BOLD & ~A_DIM);
+ }
+ }
+ if ((to ^ from) & A_DIM) {
+ if (to & A_BOLD) M("2");
+ else {
+ M("22");
+ from &= (~A_BOLD & ~A_DIM);
+ }
+ }
+ if ((to ^ from) & A_ITALIC) M((to & A_ITALIC) ? "3" : "23");
+ if ((to ^ from) & A_UNDERSCORE) M((to & A_UNDERSCORE) ? "4" : "24");
+ if ((to ^ from) & A_BLINK) M((to & A_BLINK) ? "5" : "25");
+ if ((to ^ from) & A_REVERSE) M((to & A_REVERSE) ? "7" : "27");
+ }
+
+chg_colors:;
+
+ int f_fg = ATTR_FG(from);
+ int f_bg = ATTR_BG(from);
+ int t_fg = ATTR_FG(to);
+ int t_bg = ATTR_BG(to);
+
+ int chg_fg = (t_fg != f_fg);
+ int chg_bg = (t_bg != f_bg);
+ if (chg_fg || chg_bg) {
+ if (chg_attr) vui_outc(';');
+ else vui_outs(CSI);
+ if (chg_fg) {
+ vui_outc(t_fg > 7 ? '9' : '3');
+ vui_outc((t_fg & 7) + '0');
+ }
+ if (chg_bg) {
+ if (chg_fg) vui_outc(';');
+ if (t_bg > 7) {
+ vui_outs("10");
+ } else {
+ vui_outc('4');
+ }
+ vui_outc((t_bg & 7) + '0');
+ }
+ }
+
+ if (chg_fg || chg_bg || chg_attr) vui_outc('m');
+ *ptr = to;
+}
+
+void vui_scroll_buf(VuiBuffer *b, int dx, int dy);
+
+static VuiAttr attr_last = ATTR_DEFAULT;
+static unsigned cur_x = 0;
+static unsigned cur_y = 0;
+
+static void emit_scroll_x(u32 y) {
+ if (win.scroll_x < 0) {
+ /* delete chars at start of line */
+ attr_chg(&attr_last, ATTR_DEFAULT);
+ curs_move(&cur_x, &cur_y, 0, y);
+ vui_outf(CSI "%dP", -win.scroll_x);
+ } else if (win.scroll_x > 0) {
+ /* insert spaces at start of line */
+ attr_chg(&attr_last, ATTR_DEFAULT);
+ curs_move(&cur_x, &cur_y, 0, y);
+ vui_outf(CSI "%d@", win.scroll_x);
+ }
+}
+
+static void emit_scroll_y(void) {
+ /* xterm has \x1b[S and \x1b[T to scroll directly */
+ /* but these don't work in linux vtty */
+ if (win.scroll_y < 0) {
+ /* delete lines at top */
+ curs_move(&cur_x, &cur_y, 0, 0);
+ attr_chg(&attr_last, ATTR_DEFAULT);
+ if (win.scroll_y == -1) vui_outs(CSI "M");
+ else vui_outf(CSI "%dM", -win.scroll_y);
+ } else if (win.scroll_y > 0) {
+ /* insert blank lines at top */
+ curs_move(&cur_x, &cur_y, 0, 0);
+ attr_chg(&attr_last, ATTR_DEFAULT);
+ if (win.scroll_y == 1) vui_outs(CSI "L");
+ else vui_outf(CSI "%dL", win.scroll_y);
+ }
+}
+
+void vui_blit(void) {
+ VuiBuffer *front = win.front;
+ VuiBuffer *back = win.back;
+
+ if (win.redraw_all) {
+ vui_outs(CSI "H" CSI "2J" CSI "0m");
+ attr_last = ATTR_DEFAULT;
+ unsigned max = COLS * LINES;
+ for (unsigned i = 0; i < max; i++) {
+ attr_chg(&attr_last, front->attr[i]);
+ vui_outvc(front->chr[i]);
+ }
+ vui_outs(CSI "H");
+ cur_x = 0;
+ cur_y = 0;
+ win.redraw_all = 0;
+ win.scroll_x = 0;
+ win.scroll_y = 0;
+ goto end;
+ }
+
+ emit_scroll_y();
+ vui_scroll_buf(back, win.scroll_x, win.scroll_y);
+ for (unsigned y = 0; y < LINES; y++) {
+ unsigned x = 0;
+ emit_scroll_x(y);
+ while (x < COLS) {
+ while (x < COLS && bchr_equiv(back, front, (y * COLS) + x)) x++;
+ if (x >= COLS) break;
+ unsigned x0 = x;
+ VuiAttr a = ATTR(x0, y);
+ while (x < COLS && !bchr_equiv(back, front, (y * COLS) + x) && BATTR(front, x, y) == a) x++;
+ if (x0 != x) {
+ curs_move(&cur_x, &cur_y, x0, y);
+ attr_chg(&attr_last, a);
+ vui_outvcn(&CHR(x0, y), x - x0);
+ cur_x = x;
+ }
+ }
+ }
+
+end:
+ win.scroll_x = 0;
+ win.scroll_y = 0;
+
+ if (win.curs_vis) {
+ curs_move(&cur_x, &cur_y, win.curs_x, win.curs_y);
+ }
+
+ vui_out_flush();
+ memcpy(back->chr, front->chr, sizeof(*back->chr) * (back->width * back->height));
+ memcpy(back->attr, front->attr, sizeof(*back->attr) * (back->width * back->height));
+ win.front = back;
+ win.back = front;
+}
+
+void vui_chra(int x, int y, VuiChar c, VuiAttr a) {
+ if (x >= 0 && x < (int)COLS && y >= 0 && y < (int)LINES) {
+ CHR(x, y) = c;
+ ATTR(x, y) = a;
+ }
+}
+
+void vui_chr(int x, int y, VuiChar c) {
+ vui_chra(x, y, c, ATTR_DEFAULT);
+}
+
+static void truncate_span(int *x, unsigned *nptr) {
+ int n = (int)*nptr;
+ if (*x < 0) {
+ n += *x;
+ *x = 0;
+ }
+ if (*x >= (int)COLS) {
+ *nptr = 0;
+ return;
+ }
+ if (*x + n > (int)COLS) {
+ n = COLS - *x;
+ }
+ if (n < 0) n = 0;
+ assert(n >= 0);
+ //assert(*x + n < (int)COLS);
+ *nptr = n;
+}
+
+void vui_putsna(int x, int y, const char *s, unsigned srcn, VuiAttr a) {
+ u32 n = utf8_decode_len(s, srcn);
+ truncate_span(&x, &n);
+ if (n < 1 || y < 0 || y >= (int)LINES) return;
+ utf8_decode(&CHR(x, y), s, n);
+ for (uint16_t *pa = &ATTR(x, y); n--;) *pa++ = a;
+}
+
+void vui_putsn(int x, int y, const char *s, unsigned n) {
+ vui_putsna(x, y, s, n, ATTR_DEFAULT);
+}
+
+void vui_putsa(int x, int y, const char *s, VuiAttr a) {
+ vui_putsna(x, y, s, strlen(s), a);
+}
+
+void vui_puts(int x, int y, const char *s) {
+ vui_putsn(x, y, s, strlen(s));
+}
+
+int vui_avprintf(int x, int y, VuiAttr a, const char *fmt, va_list ap) {
+ va_list ap2;
+ va_copy(ap2, ap);
+ int n = vsnprintf(NULL, 0, fmt, ap);
+ if (x < 0) x = COLS + x - (n - 1);
+ if (y < 0) y = LINES + y;
+ if (x + n > (int)COLS) n = COLS - x;
+ if (x < (int)COLS && y < (int)LINES && n > 0) {
+ char buf[n + 1];
+ vsnprintf(buf, n + 1, fmt, ap2);
+ vui_putsna(x, y, buf, n, a);
+ }
+ va_end(ap2);
+ return n;
+}
+
+int vui_aprintf(int x, int y, VuiAttr a, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ int r = vui_avprintf(x, y, a, fmt, ap);
+ va_end(ap);
+ return r;
+}
+
+int vui_printf(int x, int y, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ int r = vui_avprintf(x, y, ATTR_DEFAULT, fmt, ap);
+ va_end(ap);
+ return r;
+}
+
+int vui_wait_for_input(int ms) {
+ sigset_t set;
+ sigemptyset(&set);
+ sigaddset(&set, SIGWINCH);
+ sigprocmask(SIG_BLOCK, &set, NULL);
+ int r = poll(&(struct pollfd) { .fd = STDIN_FILENO, .events = POLLIN }, 1, ms);
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ return r;
+}
+
+int vui_has_input(void) {
+ return vui_wait_for_input(0);
+}
+
+void vui_scroll_buf(VuiBuffer *b, int dx, int dy) {
+ if ((unsigned)abs(dx) >= b->width || (unsigned)abs(dy) >= b->height) {
+ clear_buf(b);
+ return;
+ }
+ if (dy > 0) {
+ memmove(b->chr + (b->width * dy), b->chr, sizeof(*b->chr) * b->width * (b->height - dy));
+ memmove(b->attr + (b->width * dy), b->attr, sizeof(*b->attr) * b->width * (b->height - dy));
+ for (int y = 0; y < dy; y++) vui_clrtoeol(b, 0, y);
+ } else if (dy < 0) {
+ memmove(b->chr, b->chr + (b->width * -dy), sizeof(*b->chr) * b->width * (b->height + dy));
+ memmove(b->attr, b->attr + (b->width * -dy), sizeof(*b->attr) * b->width * (b->height + dy));
+ for (int y = b->height - 1; y > (int)b->height + dy - 1; y--) vui_clrtoeol(b, 0, y);
+ }
+ if (dx > 0) {
+ for (unsigned i = 0; i < b->height; i++) {
+ memmove(b->chr + (i * b->width) + dx, b->chr + (i * b->width), sizeof(*b->chr) * (b->width - dx));
+ memmove(b->attr + (i * b->width) + dx, b->attr + (i * b->width), sizeof(*b->attr) * (b->width - dx));
+ vui_clrspan(b, 0, dx, i);
+ }
+ } else if (dx < 0) {
+ for (unsigned i = 0; i < b->height; i++) {
+ memmove(b->chr + (i * b->width), b->chr + (i * b->width) - dx, sizeof(*b->chr) * (b->width + dx));
+ memmove(b->attr + (i * b->width), b->attr + (i * b->width) - dx, sizeof(*b->attr) * (b->width + dx));
+ vui_clrspan(b, b->width + dx - 1, b->width, i);
+ }
+
+ }
+}
+
+void vui_scroll(int dx, int dy) {
+ vui_scroll_buf(win.front, dx, dy);
+ win.scroll_x += dx;
+ win.scroll_y += dy;
+}
+
+static unsigned getk(void) {
+ char c;
+ int r;
+ do {
+ r = read(STDIN_FILENO, &c, 1);
+ } while (r == -1 && errno == EINTR);
+ if (r == -1) return KEY_EOF;
+ return c;
+}
+
+static unsigned getk_utf8(uint8_t start) {
+ unsigned n = stdc_leading_ones(start);
+ if (n > 4) return KEY_INVALID;
+ static const uint8_t cpmask[4] = { 0x7f, 0x3f, 0x1f, 0xf };
+ uint32_t ch = start & cpmask[n - 1];
+ while (--n) {
+ uint8_t k = getk();
+ if ((k & 0xc0) != 0x80) return KEY_INVALID;
+ ch = (ch << 6) | (k & 0x3f);
+ }
+ return ch;
+}
+
+static VuiKey bad_key(void) {
+ /* yell */
+ putchar('\a');
+ fflush(stdout);
+ return KEY_INVALID;
+}
+
+static VuiKey meta_key(u32 c) {
+ switch (c) {
+ case '2': return esc_key(getk()) | KEY_SHIFT_BIT; break;
+ case '3': return esc_key(getk()) | KEY_ALT_BIT; break;
+ case '4': return esc_key(getk()) | KEY_ALT_BIT | KEY_SHIFT_BIT; break;
+ case '5': return esc_key(getk()) | KEY_CTRL_BIT; break;
+ case '6': return esc_key(getk()) | KEY_CTRL_BIT | KEY_SHIFT_BIT; break;
+ case '7': return esc_key(getk()) | KEY_CTRL_BIT | KEY_ALT_BIT; break;
+ case '8': return esc_key(getk()) | KEY_CTRL_BIT | KEY_SHIFT_BIT | KEY_ALT_BIT; break;
+ case '9': return esc_key(getk()) | KEY_META_BIT; break;
+ case '1':
+ switch (getk()) {
+ case '0': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT; break;
+ case '1': return esc_key(getk()) | KEY_META_BIT | KEY_ALT_BIT; break;
+ case '3': return esc_key(getk()) | KEY_META_BIT | KEY_CTRL_BIT; break;
+ case '4': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT | KEY_CTRL_BIT; break;
+ case '5': return esc_key(getk()) | KEY_META_BIT | KEY_ALT_BIT | KEY_CTRL_BIT; break;
+ case '6': return esc_key(getk()) | KEY_META_BIT | KEY_SHIFT_BIT | KEY_ALT_BIT | KEY_CTRL_BIT; break;
+ default: return bad_key();
+ }
+ default: return bad_key();
+ }
+}
+
+static VuiKey esc_key(u32 c) {
+ switch (c) {
+ case 'P': return KEY_F1; break;
+ case 'Q': return KEY_F2; break;
+ case 'R': return KEY_F3; break;
+ case 'S': return KEY_F4; break;
+ case '1':
+ switch (getk()) {
+ case ';': return meta_key(getk());
+ case '5':
+ default: return bad_key();
+ }
+ break;
+ case 'A': return KEY_UP;
+ case 'B': return KEY_DOWN;
+ case 'C': return KEY_RIGHT;
+ case 'D': return KEY_LEFT;
+ case 'H': return KEY_HOME;
+ case 'F': return KEY_END;
+ case '5':
+ if (getk() != '~') return bad_key();
+ return KEY_PGUP;
+ case '6':
+ if (getk() != '~') return bad_key();
+ return KEY_PGDN;
+ default: return bad_key();
+ }
+}
+
+VuiKey vui_key(void) {
+ int c = getk();
+ switch (c) {
+ case '\n':
+ return KEY_RET;
+ case '\b': case 0x7f:
+ return KEY_BKSP;
+ case '\x1b':
+ if (vui_has_input()) {
+ c = getk();
+ switch (c) {
+ case 'O':
+ switch (getk()) {
+ case 'P': return KEY_F1; break;
+ case 'Q': return KEY_F2; break;
+ case 'R': return KEY_F3; break;
+ case 'S': return KEY_F4; break;
+ default: return bad_key(); break;
+ }
+ case '[': return esc_key(getk());
+ default: return bad_key();
+ }
+ } else {
+ return KEY_ESC;
+ }
+ default:
+ return c & 0x80 ? getk_utf8(c) : c;
+ }
+}
diff --git a/vui.h b/vui.h
new file mode 100644
index 0000000..c8f7a50
--- /dev/null
+++ b/vui.h
@@ -0,0 +1,144 @@
+#ifndef VUI_H
+#define VUI_H
+
+#include <stdarg.h>
+#include <stdint.h>
+
+typedef enum {
+ KEY_EOF = 0,
+ KEY_BKSP = 0x08,
+ KEY_RET = 0x0d,
+ KEY_ESC = 0x1b,
+ KEY_DEL = 0x7f,
+ KEY_UTF8_MAX = 0x10ffff,
+
+ KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
+ KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDN,
+
+ KEY_F1, KEY_F2, KEY_F3, KEY_F4,
+ KEY_F5, KEY_F6, KEY_F7, KEY_F8,
+ KEY_F9, KEY_F10, KEY_F11, KEY_F12,
+
+ KEY_SHIFT_BIT = 0x200000,
+ KEY_CTRL_BIT = 0x400000,
+ KEY_ALT_BIT = 0x800000,
+ KEY_META_BIT = 0x1000000,
+ KEY_INVALID = 0x7fffffff,
+
+ KEY_CTRL_MASK = 0xe000000,
+ KEY_BASE_MASK = ~KEY_CTRL_MASK,
+} VuiKey;
+
+typedef enum {
+ FG_BLACK = 0,
+ FG_RED = 1,
+ FG_GREEN = 2,
+ FG_YELLOW = 3,
+ FG_BLUE = 4,
+ FG_MAGENTA = 5,
+ FG_CYAN = 6,
+ FG_WHITE = 7,
+
+ FG_BBLACK = FG_BLACK + 8,
+ FG_BRED = FG_RED + 8,
+ FG_BGREEN = FG_GREEN + 8,
+ FG_BYELLOW = FG_YELLOW + 8,
+ FG_BBLUE = FG_BLUE + 8,
+ FG_BMAGENTA = FG_MAGENTA + 8,
+ FG_BCYAN = FG_CYAN + 8,
+ FG_BWHITE = FG_WHITE + 8,
+
+ BG_BLACK = FG_BLACK << 4,
+ BG_RED = FG_RED << 4,
+ BG_GREEN = FG_GREEN << 4,
+ BG_YELLOW = FG_YELLOW << 4,
+ BG_BLUE = FG_BLUE << 4,
+ BG_MAGENTA = FG_MAGENTA << 4,
+ BG_CYAN = FG_CYAN << 4,
+ BG_WHITE = FG_WHITE << 4,
+
+ BG_BBLACK = FG_BBLACK << 4,
+ BG_BRED = FG_BRED << 4,
+ BG_BGREEN = FG_BGREEN << 4,
+ BG_BYELLOW = FG_BYELLOW << 4,
+ BG_BBLUE = FG_BBLUE << 4,
+ BG_BMAGENTA = FG_BMAGENTA << 4,
+ BG_BCYAN = FG_BCYAN << 4,
+ BG_BWHITE = FG_BWHITE << 4,
+
+ A_BOLD = 1 << 8,
+ A_DIM = 1 << 9,
+ A_ITALIC = 1 << 10,
+ A_UNDERSCORE = 1 << 11,
+ A_BLINK = 1 << 12,
+ A_REVERSE = 1 << 13,
+} VuiAttr;
+
+#define ATTR_FG(a) ((a) & 0xf)
+#define ATTR_BG(a) (((a)>>4) & 0xf)
+#define ATTR_A(a) ((a) & ~0xff)
+#define ATTR_DEFAULT (FG_WHITE | BG_BLACK)
+
+typedef uint32_t VuiChar;
+
+typedef struct {
+ unsigned width, height;
+ VuiChar *chr;
+ uint16_t *attr;
+} VuiBuffer;
+
+typedef struct {
+ unsigned width, height;
+ VuiBuffer buf1, buf2;
+ VuiBuffer *front, *back;
+ void (*redraw_fn)(void *ctx);
+ void *redraw_ctx;
+ int redraw_all;
+ int scroll_x, scroll_y;
+ int curs_vis, curs_x, curs_y;
+} VuiWindow;
+
+VuiWindow *vui_win(void);
+
+#define LINES (vui_win()->height)
+#define COLS (vui_win()->width)
+
+#define BCHR(b,x,y) ((b)->chr[(x) + (y) * (b)->width])
+#define BATTR(b,x,y) ((b)->attr[(x) + (y) * (b)->width])
+#define CHR(x,y) BCHR(vui_win()->front, x, y)
+#define ATTR(x,y) BATTR(vui_win()->front, x, y)
+
+void vui_init(void);
+void vui_fini(void);
+
+void vui_blit(void);
+void vui_clear(void);
+void vui_scroll(int dx, int dy);
+
+void vui_chr(int x, int y, VuiChar c);
+void vui_chra(int x, int y, VuiChar c, VuiAttr a);
+
+void vui_puts(int x, int y, const char *s);
+void vui_putsa(int x, int y, const char *s, VuiAttr a);
+void vui_putsn(int x, int y, const char *s, unsigned n);
+void vui_putsna(int x, int y, const char *s, unsigned n, VuiAttr a);
+
+int vui_avprintf(int x, int y, VuiAttr a, const char *fmt, va_list ap);
+int vui_aprintf(int x, int y, VuiAttr a, const char *fmt, ...);
+int vui_printf(int x, int y, const char *fmt, ...);
+
+void vui_fill(VuiChar c, VuiAttr a);
+void vui_fill_rect(VuiChar c, VuiAttr a, int x0, int y0, int width, int height);
+
+void vui_curs_vis(int vis);
+void vui_curs_pos(int x, int y);
+
+void vui_redraw_fn(void (*fn)(void *ctx));
+void vui_redraw_ctx(void *ctx);
+
+int vui_wait_for_input(int ms);
+int vui_has_input(void);
+
+VuiKey vui_key(void);
+
+#endif