]> git.mdlowis.com Git - proto/pick.git/commitdiff
initial commit
authorMichael D. Lowis <mike.lowis@gentex.com>
Wed, 13 Dec 2017 17:01:10 +0000 (12:01 -0500)
committerMichael D. Lowis <mike.lowis@gentex.com>
Wed, 13 Dec 2017 17:01:10 +0000 (12:01 -0500)
build.sh [new file with mode: 0755]
bytebuffer.inl [new file with mode: 0644]
c.h [new file with mode: 0644]
input.inl [new file with mode: 0644]
pick.c [new file with mode: 0644]
term.inl [new file with mode: 0644]
termbox.c [new file with mode: 0644]
termbox.h [new file with mode: 0644]
utf8.c [new file with mode: 0644]
vec.h [new file with mode: 0644]

diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..8a183dc
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+gcc -D_XOPEN_SOURCE *.c -I . -o pick
diff --git a/bytebuffer.inl b/bytebuffer.inl
new file mode 100644 (file)
index 0000000..c476742
--- /dev/null
@@ -0,0 +1,78 @@
+struct bytebuffer {
+       char *buf;
+       int len;
+       int cap;
+};
+
+static void bytebuffer_reserve(struct bytebuffer *b, int cap) {
+       if (b->cap >= cap) {
+               return;
+       }
+
+       // prefer doubling capacity
+       if (b->cap * 2 >= cap) {
+               cap = b->cap * 2;
+       }
+
+       char *newbuf = malloc(cap);
+       if (b->len > 0) {
+               // copy what was there, b->len > 0 assumes b->buf != null
+               memcpy(newbuf, b->buf, b->len);
+       }
+       if (b->buf) {
+               // in case there was an allocated buffer, free it
+               free(b->buf);
+       }
+       b->buf = newbuf;
+       b->cap = cap;
+}
+
+static void bytebuffer_init(struct bytebuffer *b, int cap) {
+       b->cap = 0;
+       b->len = 0;
+       b->buf = 0;
+
+       if (cap > 0) {
+               b->cap = cap;
+               b->buf = malloc(cap); // just assume malloc works always
+       }
+}
+
+static void bytebuffer_free(struct bytebuffer *b) {
+       if (b->buf)
+               free(b->buf);
+}
+
+static void bytebuffer_clear(struct bytebuffer *b) {
+       b->len = 0;
+}
+
+static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) {
+       bytebuffer_reserve(b, b->len + len);
+       memcpy(b->buf + b->len, data, len);
+       b->len += len;
+}
+
+static void bytebuffer_puts(struct bytebuffer *b, const char *str) {
+       bytebuffer_append(b, str, strlen(str));
+}
+
+static void bytebuffer_resize(struct bytebuffer *b, int len) {
+       bytebuffer_reserve(b, len);
+       b->len = len;
+}
+
+static void bytebuffer_flush(struct bytebuffer *b, int fd) {
+       write(fd, b->buf, b->len);
+       bytebuffer_clear(b);
+}
+
+static void bytebuffer_truncate(struct bytebuffer *b, int n) {
+       if (n <= 0)
+               return;
+       if (n > b->len)
+               n = b->len;
+       const int nmove = b->len - n;
+       memmove(b->buf, b->buf+n, nmove);
+       b->len -= n;
+}
diff --git a/c.h b/c.h
new file mode 100644 (file)
index 0000000..086d347
--- /dev/null
+++ b/c.h
@@ -0,0 +1,113 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <errno.h>
+
+#define nelem(x) (sizeof(x) / sizeof((x)[0]))
+
+typedef signed char        schar;
+typedef unsigned char      uchar;
+typedef unsigned short     ushort;
+typedef unsigned int       uint;
+typedef unsigned long      ulong;
+typedef unsigned long long uvlong;
+typedef signed long long   vlong;
+
+typedef uint8_t  uint8;
+typedef uint16_t uint16;
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+
+typedef int8_t  int8;
+typedef int16_t int16;
+typedef int32_t int32;
+typedef int64_t int64;
+
+typedef uintptr_t uintptr;
+typedef intptr_t  intptr;
+
+typedef ulong size_t;
+
+typedef enum { false = 0, true = 1 } bool;
+
+typedef __builtin_va_list va_list;
+#define va_start(v,l) __builtin_va_start(v,l)
+#define va_end(v)     __builtin_va_end(v)
+#define va_arg(v,l)   __builtin_va_arg(v,l)
+#define va_copy(d,s)  __builtin_va_copy(d,s)
+
+/*****************************************************************************/
+
+extern char* ARGV0;
+
+static inline char* _getopt_(int* p_argc, char*** p_argv) {
+    if (!(*p_argv)[0][1] && !(*p_argv)[1]) {
+        return (char*)0;
+    } else if ((*p_argv)[0][1]) {
+        return &(*p_argv)[0][1];
+    } else {
+        *p_argv = *p_argv + 1;
+        *p_argc = *p_argc - 1;
+        return (*p_argv)[0];
+    }
+}
+
+#define OPTBEGIN                                                              \
+    for (                                                                     \
+        ARGV0 = *argv, argc--, argv++;                                        \
+        argv[0] && argv[0][1] && argv[0][0] == '-';                           \
+        argc--, argv++                                                        \
+    ) {                                                                       \
+        int brk_; char argc_ , **argv_, *optarg_;                             \
+        if (argv[0][1] == '-' && !argv[0][2]) {                               \
+            argv++, argc--; break;                                            \
+        }                                                                     \
+        for (brk_=0, argv[0]++, argv_=argv; argv[0][0] && !brk_; argv[0]++) { \
+            if (argv_ != argv) break;                                         \
+            argc_ = argv[0][0];                                               \
+            switch (argc_)
+
+#define OPTEND }}
+
+#define OPTC() (argc_)
+
+#define OPTARG() \
+    (optarg_ = _getopt_(&argc,&argv), brk_ = (optarg_!=0), optarg_)
+
+#define EOPTARG(code) \
+    (optarg_ = _getopt_(&argc,&argv), \
+    (!optarg_ ? ((code), abort(), (char*)0) : (brk_ = 1, optarg_)))
+
+#define OPTNUM                                        \
+    case '0': case '1': case '2': case '3': case '4': \
+    case '5': case '6': case '7': case '8': case '9':
+
+#define OPTLONG case '-'
+
+/******************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static char* rdline(FILE* input) {
+    size_t size  = 8;
+    size_t index = 0;
+    char*  str   = (char*)malloc(size);
+    memset(str, 0, 8);
+    if (feof(input)) {
+        free(str);
+        return NULL;
+    }
+    while (true) {
+        char ch = fgetc(input);
+        if (ch == EOF) break;
+        str[index++] = ch;
+        str[index]   = '\0';
+        if (index+1 >= size) {
+            size = size << 1;
+            str  = realloc(str, size);
+        }
+        if (ch == '\n') break;
+    }
+    return str;
+}
diff --git a/input.inl b/input.inl
new file mode 100644 (file)
index 0000000..a4dbfca
--- /dev/null
+++ b/input.inl
@@ -0,0 +1,228 @@
+// if s1 starts with s2 returns true, else false
+// len is the length of s1
+// s2 should be null-terminated
+static bool starts_with(const char *s1, int len, const char *s2)
+{
+       int n = 0;
+       while (*s2 && n < len) {
+               if (*s1++ != *s2++)
+                       return false;
+               n++;
+       }
+       return *s2 == 0;
+}
+
+static int parse_mouse_event(struct tb_event *event, const char *buf, int len) {
+       if (len >= 6 && starts_with(buf, len, "\033[M")) {
+               // X10 mouse encoding, the simplest one
+               // \033 [ M Cb Cx Cy
+               int b = buf[3] - 32;
+               switch (b & 3) {
+               case 0:
+                       if ((b & 64) != 0)
+                               event->key = TB_KEY_MOUSE_WHEEL_UP;
+                       else
+                               event->key = TB_KEY_MOUSE_LEFT;
+                       break;
+               case 1:
+                       if ((b & 64) != 0)
+                               event->key = TB_KEY_MOUSE_WHEEL_DOWN;
+                       else
+                               event->key = TB_KEY_MOUSE_MIDDLE;
+                       break;
+               case 2:
+                       event->key = TB_KEY_MOUSE_RIGHT;
+                       break;
+               case 3:
+                       event->key = TB_KEY_MOUSE_RELEASE;
+                       break;
+               default:
+                       return -6;
+               }
+               event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
+               if ((b & 32) != 0)
+                       event->mod |= TB_MOD_MOTION;
+
+               // the coord is 1,1 for upper left
+               event->x = (uint8_t)buf[4] - 1 - 32;
+               event->y = (uint8_t)buf[5] - 1 - 32;
+
+               return 6;
+       } else if (starts_with(buf, len, "\033[<") || starts_with(buf, len, "\033[")) {
+               // xterm 1006 extended mode or urxvt 1015 extended mode
+               // xterm: \033 [ < Cb ; Cx ; Cy (M or m)
+               // urxvt: \033 [ Cb ; Cx ; Cy M
+               int i, mi = -1, starti = -1;
+               int isM, isU, s1 = -1, s2 = -1;
+               int n1 = 0, n2 = 0, n3 = 0;
+
+               for (i = 0; i < len; i++) {
+                       // We search the first (s1) and the last (s2) ';'
+                       if (buf[i] == ';') {
+                               if (s1 == -1)
+                                       s1 = i;
+                               s2 = i;
+                       }
+
+                       // We search for the first 'm' or 'M'
+                       if ((buf[i] == 'm' || buf[i] == 'M') && mi == -1) {
+                               mi = i;
+                               break;
+                       }
+               }
+               if (mi == -1)
+                       return 0;
+
+               // whether it's a capital M or not
+               isM = (buf[mi] == 'M');
+
+               if (buf[2] == '<') {
+                       isU = 0;
+                       starti = 3;
+               } else {
+                       isU = 1;
+                       starti = 2;
+               }
+
+               if (s1 == -1 || s2 == -1 || s1 == s2)
+                       return 0;
+
+               n1 = strtoul(&buf[starti], NULL, 10);
+               n2 = strtoul(&buf[s1 + 1], NULL, 10);
+               n3 = strtoul(&buf[s2 + 1], NULL, 10);
+               
+               if (isU)
+                       n1 -= 32;
+
+               switch (n1 & 3) {
+               case 0:
+                       if ((n1&64) != 0) {
+                               event->key = TB_KEY_MOUSE_WHEEL_UP;
+                       } else {
+                               event->key = TB_KEY_MOUSE_LEFT;
+                       }
+                       break;
+               case 1:
+                       if ((n1&64) != 0) {
+                               event->key = TB_KEY_MOUSE_WHEEL_DOWN;
+                       } else {
+                               event->key = TB_KEY_MOUSE_MIDDLE;
+                       }
+                       break;
+               case 2:
+                       event->key = TB_KEY_MOUSE_RIGHT;
+                       break;
+               case 3:
+                       event->key = TB_KEY_MOUSE_RELEASE;
+                       break;
+               default:
+                       return mi + 1;
+               }
+
+               if (!isM) {
+                       // on xterm mouse release is signaled by lowercase m
+                       event->key = TB_KEY_MOUSE_RELEASE;
+               }
+
+               event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default
+               if ((n1&32) != 0)
+                       event->mod |= TB_MOD_MOTION;
+
+               event->x = (uint8_t)n2 - 1;
+               event->y = (uint8_t)n3 - 1;
+
+               return mi + 1;
+       }
+
+       return 0;
+}
+
+// convert escape sequence to event, and return consumed bytes on success (failure == 0)
+static int parse_escape_seq(struct tb_event *event, const char *buf, int len)
+{
+       int mouse_parsed = parse_mouse_event(event, buf, len);
+
+       if (mouse_parsed != 0)
+               return mouse_parsed;
+
+       // it's pretty simple here, find 'starts_with' match and return
+       // success, else return failure
+       int i;
+       for (i = 0; keys[i]; i++) {
+               if (starts_with(buf, len, keys[i])) {
+                       event->ch = 0;
+                       event->key = 0xFFFF-i;
+                       return strlen(keys[i]);
+               }
+       }
+       return 0;
+}
+
+static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf, int inputmode)
+{
+       const char *buf = inbuf->buf;
+       const int len = inbuf->len;
+       if (len == 0)
+               return false;
+
+       if (buf[0] == '\033') {
+               int n = parse_escape_seq(event, buf, len);
+               if (n != 0) {
+                       bool success = true;
+                       if (n < 0) {
+                               success = false;
+                               n = -n;
+                       }
+                       bytebuffer_truncate(inbuf, n);
+                       return success;
+               } else {
+                       // it's not escape sequence, then it's ALT or ESC,
+                       // check inputmode
+                       if (inputmode&TB_INPUT_ESC) {
+                               // if we're in escape mode, fill ESC event, pop
+                               // buffer, return success
+                               event->ch = 0;
+                               event->key = TB_KEY_ESC;
+                               event->mod = 0;
+                               bytebuffer_truncate(inbuf, 1);
+                               return true;
+                       } else if (inputmode&TB_INPUT_ALT) {
+                               // if we're in alt mode, set ALT modifier to
+                               // event and redo parsing
+                               event->mod = TB_MOD_ALT;
+                               bytebuffer_truncate(inbuf, 1);
+                               return extract_event(event, inbuf, inputmode);
+                       }
+                       assert(!"never got here");
+               }
+       }
+
+       // if we're here, this is not an escape sequence and not an alt sequence
+       // so, it's a FUNCTIONAL KEY or a UNICODE character
+
+       // first of all check if it's a functional key
+       if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
+           (unsigned char)buf[0] == TB_KEY_BACKSPACE2)
+       {
+               // fill event, pop buffer, return success */
+               event->ch = 0;
+               event->key = (uint16_t)buf[0];
+               bytebuffer_truncate(inbuf, 1);
+               return true;
+       }
+
+       // feh... we got utf8 here
+
+       // check if there is all bytes
+       if (len >= tb_utf8_char_length(buf[0])) {
+               /* everything ok, fill event, pop buffer, return success */
+               tb_utf8_char_to_unicode(&event->ch, buf);
+               event->key = 0;
+               bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
+               return true;
+       }
+
+       // event isn't recognized, perhaps there is not enough bytes in utf8
+       // sequence
+       return false;
+}
diff --git a/pick.c b/pick.c
new file mode 100644 (file)
index 0000000..38abde8
--- /dev/null
+++ b/pick.c
@@ -0,0 +1,162 @@
+#include <c.h>
+#include <vec.h>
+#include <termbox.h>
+#include <ctype.h>
+
+typedef struct {
+    float score;
+    char* string;
+    size_t length;
+    size_t match_start;
+    size_t match_end;
+} Choice;
+
+char* ARGV0;
+int AltFlag = 1; // Disable alternate screen usage
+char Query[1024] = {0};
+size_t QueryIdx = 0;
+vec_t Choices = {0};
+size_t ChoiceIdx = 2;
+
+int by_score(const void* a, const void* b) {
+    Choice* ca = ((Choice*)a);
+    Choice* cb = ((Choice*)b);
+    if (ca->score < cb->score)
+        return 1;
+    else if (ca->score > cb->score)
+        return -1;
+    else
+        return strcmp(ca->string, cb->string);
+}
+
+void load_choices(void) {
+    char* choice_text;
+    Choice choice = {0};
+    vec_init(&Choices, sizeof(Choice));
+    while ((choice_text = rdline(stdin)) != NULL) {
+        choice_text[strlen(choice_text)-1] = '\0';
+        if (strlen(choice_text) > 0) {
+            choice.string = choice_text;
+            choice.length = strlen(choice_text);
+            choice.score  = 1.0;
+            vec_push_back(&Choices, &choice);
+        }
+    }
+    vec_sort(&Choices, by_score);
+}
+
+char* find_match_start(char *str, int ch) {
+    for (; *str; str++)
+        if (tolower(*str) == tolower(ch))
+            return str;
+    return NULL;
+}
+
+bool match(char *string, size_t offset, size_t *start, size_t *end) {
+    char* q = Query;
+    char* s = find_match_start(&string[offset], *q);
+    char* e = s;
+    /* bail if no match for first char */
+    if (s == NULL) return 0;
+    /* find the end of the match */
+    for (; *q; q++)
+        if ((e = find_match_start(e, *q)) == NULL)
+            return false;
+    /* make note of the matching range */
+    *start = s - string;
+    *end   = e - string;
+    /* Less than or equal is used in order to obtain the left-most match. */
+    if (match(string, offset + 1, start, end) && (size_t)(e - s) <= *end - *start) {
+        *start = s - string;
+        *end   = e - string;
+    }
+    return true;
+}
+
+void score(void) {
+    for (int i = 0; i < vec_size(&Choices); i++) {
+        Choice* choice = (Choice*)vec_at(&Choices, i);
+        float qlen = (float)QueryIdx;
+        if (match(choice->string, 0, &choice->match_start, &choice->match_end)) {
+            float clen = (float)(choice->match_end - choice->match_start);
+            choice->score = qlen / clen / (float)(choice->length);
+        } else {
+            choice->match_start = 0;
+            choice->match_end   = 0;
+            choice->score       = 0.0f;
+        }
+    }
+    vec_sort(&Choices, by_score);
+}
+
+void redraw(void) {
+    tb_clear();
+    /* Draw query and cursor */
+    int max_x = (tb_width() < 1023 ? tb_width() : 1023);
+    for (int x = 0; x < max_x; x++)
+        tb_change_cell(x, 0, Query[x], TB_DEFAULT, TB_DEFAULT);
+    tb_set_cursor(QueryIdx,0);
+    /* Draw line dividing query and results */
+    for (int x = 0; x < tb_width(); x++)
+        tb_change_cell(x, 1, 0x2500, TB_DEFAULT, TB_DEFAULT);
+    /* Draw the scored and sorted results */
+    for (int i = 0, y = 2; (i < vec_size(&Choices)) && (y < tb_height()); i++) {
+        bool selected = (y == ChoiceIdx);
+        Choice* choice = (Choice*)vec_at(&Choices, i);
+        if (choice->score >= 0.0) {
+            for (int x = 0; choice->string[x] && x < tb_width(); x++) {
+                bool inmatch  = (choice->match_end && x >= choice->match_start && x <= choice->match_end);
+                tb_change_cell(x, i+2, choice->string[x],
+                    (inmatch  ? TB_UNDERLINE|TB_BLUE : TB_DEFAULT),
+                    (selected ? TB_WHITE : TB_DEFAULT));
+            }
+            for (int x = choice->length; selected && (x < tb_width()); x++)
+                tb_change_cell(x, y, ' ', TB_DEFAULT, TB_WHITE);
+            y++;
+        }
+    }
+    tb_present();
+}
+
+void filter(void) {
+    struct tb_event ev = {0};
+    tb_init_with(AltFlag ? TB_INIT_EVERYTHING : 0);
+    do {
+        if (ev.type == TB_EVENT_KEY) {
+            if (ev.key == TB_KEY_ENTER) {
+                break;
+            } else if (ev.key == TB_KEY_ESC) {
+                ChoiceIdx = SIZE_MAX;
+                break;
+            } else if (ev.key == TB_KEY_BACKSPACE || ev.key == TB_KEY_BACKSPACE2) {
+                if (QueryIdx > 0)
+                    Query[--QueryIdx] = '\0';
+            } else if (ev.key == TB_KEY_ARROW_DOWN) {
+                if (ChoiceIdx < tb_width() && ChoiceIdx <= vec_size(&Choices))
+                    ChoiceIdx++;
+            } else if (ev.key == TB_KEY_ARROW_UP) {
+                if (ChoiceIdx > 2)
+                    ChoiceIdx--;
+            } else if (ev.ch) {
+                if (QueryIdx < sizeof(Query)-1)
+                    Query[QueryIdx++] = ev.ch;
+            }
+        }
+        score();
+        redraw();
+    } while (tb_poll_event(&ev));
+    tb_shutdown();
+}
+
+int main(int argc, char** argv) {
+    OPTBEGIN {
+        case 'a': AltFlag = 0; break;
+    } OPTEND;
+    load_choices();
+    if (vec_size(&Choices) > 1)
+        filter();
+    Choice* choice = (Choice*)vec_at(&Choices, ChoiceIdx-2);
+    if (vec_size(&Choices) && ChoiceIdx != SIZE_MAX)
+        printf("%s\n", choice->string);
+    return 0;
+}
diff --git a/term.inl b/term.inl
new file mode 100644 (file)
index 0000000..7b2510a
--- /dev/null
+++ b/term.inl
@@ -0,0 +1,302 @@
+enum {
+       T_ENTER_CA,
+       T_EXIT_CA,
+       T_SHOW_CURSOR,
+       T_HIDE_CURSOR,
+       T_CLEAR_SCREEN,
+       T_SGR0,
+       T_UNDERLINE,
+       T_BOLD,
+       T_BLINK,
+       T_REVERSE,
+       T_ENTER_KEYPAD,
+       T_EXIT_KEYPAD,
+       T_ENTER_MOUSE,
+       T_EXIT_MOUSE,
+       T_FUNCS_NUM,
+};
+
+#define EUNSUPPORTED_TERM -1
+
+// rxvt-256color
+static const char *rxvt_256color_keys[] = {
+       "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *rxvt_256color_funcs[] = {
+       "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// Eterm
+static const char *eterm_keys[] = {
+       "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *eterm_funcs[] = {
+       "\0337\033[?47h", "\033[2J\033[?47l\0338", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
+};
+
+// screen
+static const char *screen_keys[] = {
+       "\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0
+};
+static const char *screen_funcs[] = {
+       "\033[?1049h", "\033[?1049l", "\033[34h\033[?25h", "\033[?25l", "\033[H\033[J", "\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// rxvt-unicode
+static const char *rxvt_unicode_keys[] = {
+       "\033[11~","\033[12~","\033[13~","\033[14~","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[7~","\033[8~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *rxvt_unicode_funcs[] = {
+       "\033[?1049h", "\033[r\033[?1049l", "\033[?25h", "\033[?25l", "\033[H\033[2J", "\033[m\033(B", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033=", "\033>", "\033[?1000h", "\033[?1000l",
+};
+
+// linux
+static const char *linux_keys[] = {
+       "\033[[A","\033[[B","\033[[C","\033[[D","\033[[E","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033[1~","\033[4~","\033[5~","\033[6~","\033[A","\033[B","\033[D","\033[C", 0
+};
+static const char *linux_funcs[] = {
+       "", "", "\033[?25h\033[?0c", "\033[?25l\033[?1c", "\033[H\033[J", "\033[0;10m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "", "", "", "",
+};
+
+// xterm
+static const char *xterm_keys[] = {
+       "\033OP","\033OQ","\033OR","\033OS","\033[15~","\033[17~","\033[18~","\033[19~","\033[20~","\033[21~","\033[23~","\033[24~","\033[2~","\033[3~","\033OH","\033OF","\033[5~","\033[6~","\033OA","\033OB","\033OD","\033OC", 0
+};
+static const char *xterm_funcs[] = {
+       "\033[?1049h", "\033[?1049l", "\033[?12l\033[?25h", "\033[?25l", "\033[H\033[2J", "\033(B\033[m", "\033[4m", "\033[1m", "\033[5m", "\033[7m", "\033[?1h\033=", "\033[?1l\033>", "\033[?1000h", "\033[?1000l",
+};
+
+static struct term {
+       const char *name;
+       const char **keys;
+       const char **funcs;
+} terms[] = {
+       {"rxvt-256color", rxvt_256color_keys, rxvt_256color_funcs},
+       {"Eterm", eterm_keys, eterm_funcs},
+       {"screen", screen_keys, screen_funcs},
+       {"rxvt-unicode", rxvt_unicode_keys, rxvt_unicode_funcs},
+       {"linux", linux_keys, linux_funcs},
+       {"xterm", xterm_keys, xterm_funcs},
+       {0, 0, 0},
+};
+
+static bool init_from_terminfo = false;
+static const char **keys;
+static const char **funcs;
+
+static int try_compatible(const char *term, const char *name,
+                         const char **tkeys, const char **tfuncs)
+{
+       if (strstr(term, name)) {
+               keys = tkeys;
+               funcs = tfuncs;
+               return 0;
+       }
+
+       return EUNSUPPORTED_TERM;
+}
+
+static int init_term_builtin(void)
+{
+       int i;
+       const char *term = getenv("TERM");
+
+       if (term) {
+               for (i = 0; terms[i].name; i++) {
+                       if (!strcmp(terms[i].name, term)) {
+                               keys = terms[i].keys;
+                               funcs = terms[i].funcs;
+                               return 0;
+                       }
+               }
+
+               /* let's do some heuristic, maybe it's a compatible terminal */
+               if (try_compatible(term, "xterm", xterm_keys, xterm_funcs) == 0)
+                       return 0;
+               if (try_compatible(term, "rxvt", rxvt_unicode_keys, rxvt_unicode_funcs) == 0)
+                       return 0;
+               if (try_compatible(term, "linux", linux_keys, linux_funcs) == 0)
+                       return 0;
+               if (try_compatible(term, "Eterm", eterm_keys, eterm_funcs) == 0)
+                       return 0;
+               if (try_compatible(term, "screen", screen_keys, screen_funcs) == 0)
+                       return 0;
+               /* let's assume that 'cygwin' is xterm compatible */
+               if (try_compatible(term, "cygwin", xterm_keys, xterm_funcs) == 0)
+                       return 0;
+       }
+
+       return EUNSUPPORTED_TERM;
+}
+
+//----------------------------------------------------------------------
+// terminfo
+//----------------------------------------------------------------------
+
+static char *read_file(const char *file) {
+       FILE *f = fopen(file, "rb");
+       if (!f)
+               return 0;
+
+       struct stat st;
+       if (fstat(fileno(f), &st) != 0) {
+               fclose(f);
+               return 0;
+       }
+
+       char *data = malloc(st.st_size);
+       if (!data) {
+               fclose(f);
+               return 0;
+       }
+
+       if (fread(data, 1, st.st_size, f) != (size_t)st.st_size) {
+               fclose(f);
+               free(data);
+               return 0;
+       }
+
+       fclose(f);
+       return data;
+}
+
+static char *terminfo_try_path(const char *path, const char *term) {
+       char tmp[4096];
+       snprintf(tmp, sizeof(tmp), "%s/%c/%s", path, term[0], term);
+       tmp[sizeof(tmp)-1] = '\0';
+       char *data = read_file(tmp);
+       if (data) {
+               return data;
+       }
+
+       // fallback to darwin specific dirs structure
+       snprintf(tmp, sizeof(tmp), "%s/%x/%s", path, term[0], term);
+       tmp[sizeof(tmp)-1] = '\0';
+       return read_file(tmp);
+}
+
+static char *load_terminfo(void) {
+       char tmp[4096];
+       const char *term = getenv("TERM");
+       if (!term) {
+               return 0;
+       }
+
+       // if TERMINFO is set, no other directory should be searched
+       const char *terminfo = getenv("TERMINFO");
+       if (terminfo) {
+               return terminfo_try_path(terminfo, term);
+       }
+
+       // next, consider ~/.terminfo
+       const char *home = getenv("HOME");
+       if (home) {
+               snprintf(tmp, sizeof(tmp), "%s/.terminfo", home);
+               tmp[sizeof(tmp)-1] = '\0';
+               char *data = terminfo_try_path(tmp, term);
+               if (data)
+                       return data;
+       }
+
+       // next, TERMINFO_DIRS
+       const char *dirs = getenv("TERMINFO_DIRS");
+       if (dirs) {
+               snprintf(tmp, sizeof(tmp), "%s", dirs);
+               tmp[sizeof(tmp)-1] = '\0';
+               char *dir = strtok(tmp, ":");
+               while (dir) {
+                       const char *cdir = dir;
+                       if (strcmp(cdir, "") == 0) {
+                               cdir = "/usr/share/terminfo";
+                       }
+                       char *data = terminfo_try_path(cdir, term);
+                       if (data)
+                               return data;
+                       dir = strtok(0, ":");
+               }
+       }
+
+       // fallback to /usr/share/terminfo
+       return terminfo_try_path("/usr/share/terminfo", term);
+}
+
+#define TI_MAGIC 0432
+#define TI_HEADER_LENGTH 12
+#define TB_KEYS_NUM 22
+
+static const char *terminfo_copy_string(char *data, int str, int table) {
+       const int16_t off = *(int16_t*)(data + str);
+       const char *src = data + table + off;
+       int len = strlen(src);
+       char *dst = malloc(len+1);
+       strcpy(dst, src);
+       return dst;
+}
+
+static const int16_t ti_funcs[] = {
+       28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
+};
+
+static const int16_t ti_keys[] = {
+       66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69,
+       70, 71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61,
+       79, 83,
+};
+
+static int init_term(void) {
+       int i;
+       char *data = load_terminfo();
+       if (!data) {
+               init_from_terminfo = false;
+               return init_term_builtin();
+       }
+
+       int16_t *header = (int16_t*)data;
+       if ((header[1] + header[2]) % 2) {
+               // old quirk to align everything on word boundaries
+               header[2] += 1;
+       }
+
+       const int str_offset = TI_HEADER_LENGTH +
+               header[1] + header[2] + 2 * header[3];
+       const int table_offset = str_offset + 2 * header[4];
+
+       keys = malloc(sizeof(const char*) * (TB_KEYS_NUM+1));
+       for (i = 0; i < TB_KEYS_NUM; i++) {
+               keys[i] = terminfo_copy_string(data,
+                       str_offset + 2 * ti_keys[i], table_offset);
+       }
+       keys[TB_KEYS_NUM] = 0;
+
+       funcs = malloc(sizeof(const char*) * T_FUNCS_NUM);
+       // the last two entries are reserved for mouse. because the table offset is
+       // not there, the two entries have to fill in manually
+       for (i = 0; i < T_FUNCS_NUM-2; i++) {
+               funcs[i] = terminfo_copy_string(data,
+                       str_offset + 2 * ti_funcs[i], table_offset);
+       }
+
+       funcs[T_FUNCS_NUM-2] = "\x1b[?1000h\x1b[?1002h\x1b[?1015h\x1b[?1006h";
+       funcs[T_FUNCS_NUM-1] = "\x1b[?1006l\x1b[?1015l\x1b[?1002l\x1b[?1000l";
+
+       init_from_terminfo = true;
+       free(data);
+       return 0;
+}
+
+static void shutdown_term(void) {
+       if (init_from_terminfo) {
+               int i;
+               for (i = 0; i < TB_KEYS_NUM; i++) {
+                       free((void*)keys[i]);
+               }
+               // the last two entries are reserved for mouse. because the table offset
+               // is not there, the two entries have to fill in manually and do not
+               // need to be freed.
+               for (i = 0; i < T_FUNCS_NUM-2; i++) {
+                       free((void*)funcs[i]);
+               }
+               free(keys);
+               free(funcs);
+       }
+}
diff --git a/termbox.c b/termbox.c
new file mode 100644 (file)
index 0000000..71257f7
--- /dev/null
+++ b/termbox.c
@@ -0,0 +1,681 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#include "termbox.h"
+
+#include "bytebuffer.inl"
+#include "term.inl"
+#include "input.inl"
+
+struct cellbuf {
+    int width;
+    int height;
+    struct tb_cell *cells;
+};
+
+#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
+#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)
+#define LAST_COORD_INIT -1
+
+static struct termios orig_tios;
+
+static struct cellbuf back_buffer;
+static struct cellbuf front_buffer;
+static struct bytebuffer output_buffer;
+static struct bytebuffer input_buffer;
+
+static int initflags = -1;
+
+static int termw = -1;
+static int termh = -1;
+
+static int inputmode = TB_INPUT_ESC;
+static int outputmode = TB_OUTPUT_NORMAL;
+
+static int inout;
+static int winch_fds[2];
+
+static int lastx = LAST_COORD_INIT;
+static int lasty = LAST_COORD_INIT;
+static int cursor_x = -1;
+static int cursor_y = -1;
+
+static uint16_t background = TB_DEFAULT;
+static uint16_t foreground = TB_DEFAULT;
+
+static void write_cursor(int x, int y);
+static void write_sgr(uint16_t fg, uint16_t bg);
+
+static void cellbuf_init(struct cellbuf *buf, int width, int height);
+static void cellbuf_resize(struct cellbuf *buf, int width, int height);
+static void cellbuf_clear(struct cellbuf *buf);
+static void cellbuf_free(struct cellbuf *buf);
+
+static void update_size(void);
+static void update_term_size(void);
+static void send_attr(uint16_t fg, uint16_t bg);
+static void send_char(int x, int y, uint32_t c);
+static void send_clear(void);
+static void sigwinch_handler(int xxx);
+static int wait_fill_event(struct tb_event *event, struct timeval *timeout);
+
+/* may happen in a different thread */
+static volatile int buffer_size_change_request;
+
+/* -------------------------------------------------------- */
+
+int tb_init_with(int flags)
+{
+    inout = open("/dev/tty", O_RDWR);
+    if (inout == -1) {
+        return TB_EFAILED_TO_OPEN_TTY;
+    }
+
+    if (init_term() < 0) {
+        close(inout);
+        return TB_EUNSUPPORTED_TERMINAL;
+    }
+
+    if (pipe(winch_fds) < 0) {
+        close(inout);
+        return TB_EPIPE_TRAP_ERROR;
+    }
+
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = sigwinch_handler;
+    sa.sa_flags = 0;
+    sigaction(SIGWINCH, &sa, 0);
+
+    tcgetattr(inout, &orig_tios);
+
+    struct termios tios;
+    memcpy(&tios, &orig_tios, sizeof(tios));
+
+    tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+                           | INLCR | IGNCR | ICRNL | IXON);
+    tios.c_oflag &= ~OPOST;
+    tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+    tios.c_cflag &= ~(CSIZE | PARENB);
+    tios.c_cflag |= CS8;
+    tios.c_cc[VMIN] = 0;
+    tios.c_cc[VTIME] = 0;
+    tcsetattr(inout, TCSAFLUSH, &tios);
+
+    bytebuffer_init(&input_buffer, 128);
+    bytebuffer_init(&output_buffer, 32 * 1024);
+
+    initflags = flags;
+    if (initflags & TB_INIT_ALTSCREEN)
+        bytebuffer_puts(&output_buffer, funcs[T_ENTER_CA]);
+    if (initflags & TB_INIT_KEYPAD)
+        bytebuffer_puts(&output_buffer, funcs[T_ENTER_KEYPAD]);
+    bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+    send_clear();
+
+    update_term_size();
+    cellbuf_init(&back_buffer, termw, termh);
+    cellbuf_init(&front_buffer, termw, termh);
+    cellbuf_clear(&back_buffer);
+    cellbuf_clear(&front_buffer);
+
+    return 0;
+}
+
+int tb_init(void)
+{
+    return tb_init_with(-1); // Enable everything
+}
+
+void tb_shutdown(void)
+{
+    if (termw == -1) {
+        fputs("tb_shutdown() should not be called twice.", stderr);
+        abort();
+    }
+
+    bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+    bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+    bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+    if (initflags & TB_INIT_ALTSCREEN)
+        bytebuffer_puts(&output_buffer, funcs[T_EXIT_CA]);
+    if (initflags & TB_INIT_KEYPAD)
+        bytebuffer_puts(&output_buffer, funcs[T_EXIT_KEYPAD]);
+    bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+    bytebuffer_flush(&output_buffer, inout);
+    tcsetattr(inout, TCSAFLUSH, &orig_tios);
+
+    shutdown_term();
+    close(inout);
+    close(winch_fds[0]);
+    close(winch_fds[1]);
+
+    cellbuf_free(&back_buffer);
+    cellbuf_free(&front_buffer);
+    bytebuffer_free(&output_buffer);
+    bytebuffer_free(&input_buffer);
+    termw = termh = -1;
+}
+
+void tb_present(void)
+{
+    int x,y,w,i;
+    struct tb_cell *back, *front;
+
+    /* invalidate cursor position */
+    lastx = LAST_COORD_INIT;
+    lasty = LAST_COORD_INIT;
+
+    if (buffer_size_change_request) {
+        update_size();
+        buffer_size_change_request = 0;
+    }
+
+    for (y = 0; y < front_buffer.height; ++y) {
+        for (x = 0; x < front_buffer.width; ) {
+            back = &CELL(&back_buffer, x, y);
+            front = &CELL(&front_buffer, x, y);
+            w = wcwidth(back->ch);
+            if (w < 1) w = 1;
+            if (memcmp(back, front, sizeof(struct tb_cell)) == 0) {
+                x += w;
+                continue;
+            }
+            memcpy(front, back, sizeof(struct tb_cell));
+            send_attr(back->fg, back->bg);
+            if (w > 1 && x >= front_buffer.width - (w - 1)) {
+                // Not enough room for wide ch, so send spaces
+                for (i = x; i < front_buffer.width; ++i) {
+                    send_char(i, y, ' ');
+                }
+            } else {
+                send_char(x, y, back->ch);
+                for (i = 1; i < w; ++i) {
+                    front = &CELL(&front_buffer, x + i, y);
+                    front->ch = 0;
+                    front->fg = back->fg;
+                    front->bg = back->bg;
+                }
+            }
+            x += w;
+        }
+    }
+    if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+        write_cursor(cursor_x, cursor_y);
+    bytebuffer_flush(&output_buffer, inout);
+}
+
+void tb_set_cursor(int cx, int cy)
+{
+    if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
+        bytebuffer_puts(&output_buffer, funcs[T_SHOW_CURSOR]);
+
+    if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
+        bytebuffer_puts(&output_buffer, funcs[T_HIDE_CURSOR]);
+
+    cursor_x = cx;
+    cursor_y = cy;
+    if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+        write_cursor(cursor_x, cursor_y);
+}
+
+void tb_put_cell(int x, int y, const struct tb_cell *cell)
+{
+    if ((unsigned)x >= (unsigned)back_buffer.width)
+        return;
+    if ((unsigned)y >= (unsigned)back_buffer.height)
+        return;
+    CELL(&back_buffer, x, y) = *cell;
+}
+
+void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg)
+{
+    struct tb_cell c = {ch, fg, bg};
+    tb_put_cell(x, y, &c);
+}
+
+void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells)
+{
+    if (x + w < 0 || x >= back_buffer.width)
+        return;
+    if (y + h < 0 || y >= back_buffer.height)
+        return;
+    int xo = 0, yo = 0, ww = w, hh = h;
+    if (x < 0) {
+        xo = -x;
+        ww -= xo;
+        x = 0;
+    }
+    if (y < 0) {
+        yo = -y;
+        hh -= yo;
+        y = 0;
+    }
+    if (ww > back_buffer.width - x)
+        ww = back_buffer.width - x;
+    if (hh > back_buffer.height - y)
+        hh = back_buffer.height - y;
+
+    int sy;
+    struct tb_cell *dst = &CELL(&back_buffer, x, y);
+    const struct tb_cell *src = cells + yo * w + xo;
+    size_t size = sizeof(struct tb_cell) * ww;
+
+    for (sy = 0; sy < hh; ++sy) {
+        memcpy(dst, src, size);
+        dst += back_buffer.width;
+        src += w;
+    }
+}
+
+struct tb_cell *tb_cell_buffer(void)
+{
+    return back_buffer.cells;
+}
+
+int tb_poll_event(struct tb_event *event)
+{
+    return wait_fill_event(event, 0);
+}
+
+int tb_peek_event(struct tb_event *event, int timeout)
+{
+    struct timeval tv;
+    tv.tv_sec = timeout / 1000;
+    tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
+    return wait_fill_event(event, &tv);
+}
+
+int tb_width(void)
+{
+    return termw;
+}
+
+int tb_height(void)
+{
+    return termh;
+}
+
+void tb_clear(void)
+{
+    if (buffer_size_change_request) {
+        update_size();
+        buffer_size_change_request = 0;
+    }
+    cellbuf_clear(&back_buffer);
+}
+
+int tb_select_input_mode(int mode)
+{
+    if (mode) {
+        if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == 0)
+            mode |= TB_INPUT_ESC;
+
+        /* technically termbox can handle that, but let's be nice and show here
+           what mode is actually used */
+        if ((mode & (TB_INPUT_ESC | TB_INPUT_ALT)) == (TB_INPUT_ESC | TB_INPUT_ALT))
+            mode &= ~TB_INPUT_ALT;
+
+        inputmode = mode;
+        if (mode&TB_INPUT_MOUSE) {
+            bytebuffer_puts(&output_buffer, funcs[T_ENTER_MOUSE]);
+            bytebuffer_flush(&output_buffer, inout);
+        } else {
+            bytebuffer_puts(&output_buffer, funcs[T_EXIT_MOUSE]);
+            bytebuffer_flush(&output_buffer, inout);
+        }
+    }
+    return inputmode;
+}
+
+int tb_select_output_mode(int mode)
+{
+    if (mode)
+        outputmode = mode;
+    return outputmode;
+}
+
+void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
+{
+    foreground = fg;
+    background = bg;
+}
+
+/* -------------------------------------------------------- */
+
+static int convertnum(uint32_t num, char* buf) {
+    int i, l = 0;
+    int ch;
+    do {
+        buf[l++] = '0' + (num % 10);
+        num /= 10;
+    } while (num);
+    for(i = 0; i < l / 2; i++) {
+        ch = buf[i];
+        buf[i] = buf[l - 1 - i];
+        buf[l - 1 - i] = ch;
+    }
+    return l;
+}
+
+#define WRITE_LITERAL(X) bytebuffer_append(&output_buffer, (X), sizeof(X)-1)
+#define WRITE_INT(X) bytebuffer_append(&output_buffer, buf, convertnum((X), buf))
+
+static void write_cursor(int x, int y) {
+    char buf[32];
+    WRITE_LITERAL("\033[");
+    WRITE_INT(y+1);
+    WRITE_LITERAL(";");
+    WRITE_INT(x+1);
+    WRITE_LITERAL("H");
+}
+
+static void write_sgr(uint16_t fg, uint16_t bg) {
+    char buf[32];
+
+    if (fg == TB_DEFAULT && bg == TB_DEFAULT)
+        return;
+
+    switch (outputmode) {
+    case TB_OUTPUT_256:
+    case TB_OUTPUT_216:
+    case TB_OUTPUT_GRAYSCALE:
+        WRITE_LITERAL("\033[");
+        if (fg != TB_DEFAULT) {
+            WRITE_LITERAL("38;5;");
+            WRITE_INT(fg);
+            if (bg != TB_DEFAULT) {
+                WRITE_LITERAL(";");
+            }
+        }
+        if (bg != TB_DEFAULT) {
+            WRITE_LITERAL("48;5;");
+            WRITE_INT(bg);
+        }
+        WRITE_LITERAL("m");
+        break;
+    case TB_OUTPUT_NORMAL:
+    default:
+        WRITE_LITERAL("\033[");
+        if (fg != TB_DEFAULT) {
+            WRITE_LITERAL("3");
+            WRITE_INT(fg - 1);
+            if (bg != TB_DEFAULT) {
+                WRITE_LITERAL(";");
+            }
+        }
+        if (bg != TB_DEFAULT) {
+            WRITE_LITERAL("4");
+            WRITE_INT(bg - 1);
+        }
+        WRITE_LITERAL("m");
+        break;
+    }
+}
+
+static void cellbuf_init(struct cellbuf *buf, int width, int height)
+{
+    buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
+    assert(buf->cells);
+    buf->width = width;
+    buf->height = height;
+}
+
+static void cellbuf_resize(struct cellbuf *buf, int width, int height)
+{
+    if (buf->width == width && buf->height == height)
+        return;
+
+    int oldw = buf->width;
+    int oldh = buf->height;
+    struct tb_cell *oldcells = buf->cells;
+
+    cellbuf_init(buf, width, height);
+    cellbuf_clear(buf);
+
+    int minw = (width < oldw) ? width : oldw;
+    int minh = (height < oldh) ? height : oldh;
+    int i;
+
+    for (i = 0; i < minh; ++i) {
+        struct tb_cell *csrc = oldcells + (i * oldw);
+        struct tb_cell *cdst = buf->cells + (i * width);
+        memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
+    }
+
+    free(oldcells);
+}
+
+static void cellbuf_clear(struct cellbuf *buf)
+{
+    int i;
+    int ncells = buf->width * buf->height;
+
+    for (i = 0; i < ncells; ++i) {
+        buf->cells[i].ch = ' ';
+        buf->cells[i].fg = foreground;
+        buf->cells[i].bg = background;
+    }
+}
+
+static void cellbuf_free(struct cellbuf *buf)
+{
+    free(buf->cells);
+}
+
+static void get_term_size(int *w, int *h)
+{
+    struct winsize sz;
+    memset(&sz, 0, sizeof(sz));
+
+    ioctl(inout, TIOCGWINSZ, &sz);
+
+    if (w) *w = sz.ws_col;
+    if (h) *h = sz.ws_row;
+}
+
+static void update_term_size(void)
+{
+    struct winsize sz;
+    memset(&sz, 0, sizeof(sz));
+
+    ioctl(inout, TIOCGWINSZ, &sz);
+
+    termw = sz.ws_col;
+    termh = sz.ws_row;
+}
+
+static void send_attr(uint16_t fg, uint16_t bg)
+{
+#define LAST_ATTR_INIT 0xFFFF
+    static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
+    if (fg != lastfg || bg != lastbg) {
+        bytebuffer_puts(&output_buffer, funcs[T_SGR0]);
+
+        uint16_t fgcol;
+        uint16_t bgcol;
+
+        switch (outputmode) {
+        case TB_OUTPUT_256:
+            fgcol = fg & 0xFF;
+            bgcol = bg & 0xFF;
+            break;
+
+        case TB_OUTPUT_216:
+            fgcol = fg & 0xFF; if (fgcol > 215) fgcol = 7;
+            bgcol = bg & 0xFF; if (bgcol > 215) bgcol = 0;
+            fgcol += 0x10;
+            bgcol += 0x10;
+            break;
+
+        case TB_OUTPUT_GRAYSCALE:
+            fgcol = fg & 0xFF; if (fgcol > 23) fgcol = 23;
+            bgcol = bg & 0xFF; if (bgcol > 23) bgcol = 0;
+            fgcol += 0xe8;
+            bgcol += 0xe8;
+            break;
+
+        case TB_OUTPUT_NORMAL:
+        default:
+            fgcol = fg & 0x0F;
+            bgcol = bg & 0x0F;
+        }
+
+        if (fg & TB_BOLD)
+            bytebuffer_puts(&output_buffer, funcs[T_BOLD]);
+        if (bg & TB_BOLD)
+            bytebuffer_puts(&output_buffer, funcs[T_BLINK]);
+        if (fg & TB_UNDERLINE)
+            bytebuffer_puts(&output_buffer, funcs[T_UNDERLINE]);
+        if ((fg & TB_REVERSE) || (bg & TB_REVERSE))
+            bytebuffer_puts(&output_buffer, funcs[T_REVERSE]);
+
+        write_sgr(fgcol, bgcol);
+
+        lastfg = fg;
+        lastbg = bg;
+    }
+}
+
+static void send_char(int x, int y, uint32_t c)
+{
+    char buf[7];
+    int bw = tb_utf8_unicode_to_char(buf, c);
+    buf[bw] = '\0';
+    if (x-1 != lastx || y != lasty)
+        write_cursor(x, y);
+    lastx = x; lasty = y;
+    if(!c) buf[0] = ' '; // replace 0 with whitespace
+    bytebuffer_puts(&output_buffer, buf);
+}
+
+static void send_clear(void)
+{
+    send_attr(foreground, background);
+    bytebuffer_puts(&output_buffer, funcs[T_CLEAR_SCREEN]);
+    if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
+        write_cursor(cursor_x, cursor_y);
+    bytebuffer_flush(&output_buffer, inout);
+
+    /* we need to invalidate cursor position too and these two vars are
+     * used only for simple cursor positioning optimization, cursor
+     * actually may be in the correct place, but we simply discard
+     * optimization once and it gives us simple solution for the case when
+     * cursor moved */
+    lastx = LAST_COORD_INIT;
+    lasty = LAST_COORD_INIT;
+}
+
+static void sigwinch_handler(int xxx)
+{
+    (void) xxx;
+    const int zzz = 1;
+    write(winch_fds[1], &zzz, sizeof(int));
+}
+
+static void update_size(void)
+{
+    update_term_size();
+    cellbuf_resize(&back_buffer, termw, termh);
+    cellbuf_resize(&front_buffer, termw, termh);
+    cellbuf_clear(&front_buffer);
+    send_clear();
+}
+
+static int read_up_to(int n) {
+    assert(n > 0);
+    const int prevlen = input_buffer.len;
+    bytebuffer_resize(&input_buffer, prevlen + n);
+
+    int read_n = 0;
+    while (read_n <= n) {
+        ssize_t r = 0;
+        if (read_n < n) {
+            r = read(inout, input_buffer.buf + prevlen + read_n, n - read_n);
+        }
+#ifdef __CYGWIN__
+        // While linux man for tty says when VMIN == 0 && VTIME == 0, read
+        // should return 0 when there is nothing to read, cygwin's read returns
+        // -1. Not sure why and if it's correct to ignore it, but let's pretend
+        // it's zero.
+        if (r < 0) r = 0;
+#endif
+        if (r < 0) {
+            // EAGAIN / EWOULDBLOCK shouldn't occur here
+            assert(errno != EAGAIN && errno != EWOULDBLOCK);
+            return -1;
+        } else if (r > 0) {
+            read_n += r;
+        } else {
+            bytebuffer_resize(&input_buffer, prevlen + read_n);
+            return read_n;
+        }
+    }
+    assert(!"unreachable");
+    return 0;
+}
+
+static int wait_fill_event(struct tb_event *event, struct timeval *timeout)
+{
+    // ;-)
+#define ENOUGH_DATA_FOR_PARSING 64
+    fd_set events;
+    memset(event, 0, sizeof(struct tb_event));
+
+    // try to extract event from input buffer, return on success
+    event->type = TB_EVENT_KEY;
+    if (extract_event(event, &input_buffer, inputmode))
+        return event->type;
+
+    // it looks like input buffer is incomplete, let's try the short path,
+    // but first make sure there is enough space
+    int n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+    if (n < 0)
+        return -1;
+    if (n > 0 && extract_event(event, &input_buffer, inputmode))
+        return event->type;
+
+    // n == 0, or not enough data, let's go to select
+    while (1) {
+        FD_ZERO(&events);
+        FD_SET(inout, &events);
+        FD_SET(winch_fds[0], &events);
+        int maxfd = (winch_fds[0] > inout) ? winch_fds[0] : inout;
+        int result = select(maxfd+1, &events, 0, 0, timeout);
+        if (!result)
+            return 0;
+
+        if (FD_ISSET(inout, &events)) {
+            event->type = TB_EVENT_KEY;
+            n = read_up_to(ENOUGH_DATA_FOR_PARSING);
+            if (n < 0)
+                return -1;
+
+            if (n == 0)
+                continue;
+
+            if (extract_event(event, &input_buffer, inputmode))
+                return event->type;
+        }
+        if (FD_ISSET(winch_fds[0], &events)) {
+            event->type = TB_EVENT_RESIZE;
+            int zzz = 0;
+            read(winch_fds[0], &zzz, sizeof(int));
+            buffer_size_change_request = 1;
+            get_term_size(&event->w, &event->h);
+            return TB_EVENT_RESIZE;
+        }
+    }
+}
diff --git a/termbox.h b/termbox.h
new file mode 100644 (file)
index 0000000..b52f941
--- /dev/null
+++ b/termbox.h
@@ -0,0 +1,324 @@
+#pragma once
+
+#include <stdint.h>
+
+/* for shared objects */
+#if __GNUC__ >= 4
+ #define SO_IMPORT __attribute__((visibility("default")))
+#else
+ #define SO_IMPORT
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Key constants. See also struct tb_event's key field.
+ *
+ * These are a safe subset of terminfo keys, which exist on all popular
+ * terminals. Termbox uses only them to stay truly portable.
+ */
+#define TB_KEY_F1               (0xFFFF-0)
+#define TB_KEY_F2               (0xFFFF-1)
+#define TB_KEY_F3               (0xFFFF-2)
+#define TB_KEY_F4               (0xFFFF-3)
+#define TB_KEY_F5               (0xFFFF-4)
+#define TB_KEY_F6               (0xFFFF-5)
+#define TB_KEY_F7               (0xFFFF-6)
+#define TB_KEY_F8               (0xFFFF-7)
+#define TB_KEY_F9               (0xFFFF-8)
+#define TB_KEY_F10              (0xFFFF-9)
+#define TB_KEY_F11              (0xFFFF-10)
+#define TB_KEY_F12              (0xFFFF-11)
+#define TB_KEY_INSERT           (0xFFFF-12)
+#define TB_KEY_DELETE           (0xFFFF-13)
+#define TB_KEY_HOME             (0xFFFF-14)
+#define TB_KEY_END              (0xFFFF-15)
+#define TB_KEY_PGUP             (0xFFFF-16)
+#define TB_KEY_PGDN             (0xFFFF-17)
+#define TB_KEY_ARROW_UP         (0xFFFF-18)
+#define TB_KEY_ARROW_DOWN       (0xFFFF-19)
+#define TB_KEY_ARROW_LEFT       (0xFFFF-20)
+#define TB_KEY_ARROW_RIGHT      (0xFFFF-21)
+#define TB_KEY_MOUSE_LEFT       (0xFFFF-22)
+#define TB_KEY_MOUSE_RIGHT      (0xFFFF-23)
+#define TB_KEY_MOUSE_MIDDLE     (0xFFFF-24)
+#define TB_KEY_MOUSE_RELEASE    (0xFFFF-25)
+#define TB_KEY_MOUSE_WHEEL_UP   (0xFFFF-26)
+#define TB_KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
+
+/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
+#define TB_KEY_CTRL_TILDE       0x00
+#define TB_KEY_CTRL_2           0x00 /* clash with 'CTRL_TILDE' */
+#define TB_KEY_CTRL_A           0x01
+#define TB_KEY_CTRL_B           0x02
+#define TB_KEY_CTRL_C           0x03
+#define TB_KEY_CTRL_D           0x04
+#define TB_KEY_CTRL_E           0x05
+#define TB_KEY_CTRL_F           0x06
+#define TB_KEY_CTRL_G           0x07
+#define TB_KEY_BACKSPACE        0x08
+#define TB_KEY_CTRL_H           0x08 /* clash with 'CTRL_BACKSPACE' */
+#define TB_KEY_TAB              0x09
+#define TB_KEY_CTRL_I           0x09 /* clash with 'TAB' */
+#define TB_KEY_CTRL_J           0x0A
+#define TB_KEY_CTRL_K           0x0B
+#define TB_KEY_CTRL_L           0x0C
+#define TB_KEY_ENTER            0x0D
+#define TB_KEY_CTRL_M           0x0D /* clash with 'ENTER' */
+#define TB_KEY_CTRL_N           0x0E
+#define TB_KEY_CTRL_O           0x0F
+#define TB_KEY_CTRL_P           0x10
+#define TB_KEY_CTRL_Q           0x11
+#define TB_KEY_CTRL_R           0x12
+#define TB_KEY_CTRL_S           0x13
+#define TB_KEY_CTRL_T           0x14
+#define TB_KEY_CTRL_U           0x15
+#define TB_KEY_CTRL_V           0x16
+#define TB_KEY_CTRL_W           0x17
+#define TB_KEY_CTRL_X           0x18
+#define TB_KEY_CTRL_Y           0x19
+#define TB_KEY_CTRL_Z           0x1A
+#define TB_KEY_ESC              0x1B
+#define TB_KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_3           0x1B /* clash with 'ESC' */
+#define TB_KEY_CTRL_4           0x1C
+#define TB_KEY_CTRL_BACKSLASH   0x1C /* clash with 'CTRL_4' */
+#define TB_KEY_CTRL_5           0x1D
+#define TB_KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
+#define TB_KEY_CTRL_6           0x1E
+#define TB_KEY_CTRL_7           0x1F
+#define TB_KEY_CTRL_SLASH       0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_CTRL_UNDERSCORE  0x1F /* clash with 'CTRL_7' */
+#define TB_KEY_SPACE            0x20
+#define TB_KEY_BACKSPACE2       0x7F
+#define TB_KEY_CTRL_8           0x7F /* clash with 'BACKSPACE2' */
+
+/* These are non-existing ones.
+ *
+ * #define TB_KEY_CTRL_1 clash with '1'
+ * #define TB_KEY_CTRL_9 clash with '9'
+ * #define TB_KEY_CTRL_0 clash with '0'
+ */
+
+/*
+ * Alt modifier constant, see tb_event.mod field and tb_select_input_mode function.
+ * Mouse-motion modifier
+ */
+#define TB_MOD_ALT    0x01
+#define TB_MOD_MOTION 0x02
+
+/* Colors (see struct tb_cell's fg and bg fields). */
+#define TB_DEFAULT 0x00
+#define TB_BLACK   0x01
+#define TB_RED     0x02
+#define TB_GREEN   0x03
+#define TB_YELLOW  0x04
+#define TB_BLUE    0x05
+#define TB_MAGENTA 0x06
+#define TB_CYAN    0x07
+#define TB_WHITE   0x08
+
+/* Attributes, it is possible to use multiple attributes by combining them
+ * using bitwise OR ('|'). Although, colors cannot be combined. But you can
+ * combine attributes and a single color. See also struct tb_cell's fg and bg
+ * fields.
+ */
+#define TB_BOLD      0x0100
+#define TB_UNDERLINE 0x0200
+#define TB_REVERSE   0x0400
+
+/* A cell, single conceptual entity on the terminal screen. The terminal screen
+ * is basically a 2d array of cells. It has the following fields:
+ *  - 'ch' is a unicode character
+ *  - 'fg' foreground color and attributes
+ *  - 'bg' background color and attributes
+ */
+struct tb_cell {
+    uint32_t ch;
+    uint16_t fg;
+    uint16_t bg;
+};
+
+#define TB_EVENT_KEY    1
+#define TB_EVENT_RESIZE 2
+#define TB_EVENT_MOUSE  3
+
+/* An event, single interaction from the user. The 'mod' and 'ch' fields are
+ * valid if 'type' is TB_EVENT_KEY. The 'w' and 'h' fields are valid if 'type'
+ * is TB_EVENT_RESIZE. The 'x' and 'y' fields are valid if 'type' is
+ * TB_EVENT_MOUSE. The 'key' field is valid if 'type' is either TB_EVENT_KEY
+ * or TB_EVENT_MOUSE. The fields 'key' and 'ch' are mutually exclusive; only
+ * one of them can be non-zero at a time.
+ */
+struct tb_event {
+    uint8_t type;
+    uint8_t mod; /* modifiers to either 'key' or 'ch' below */
+    uint16_t key; /* one of the TB_KEY_* constants */
+    uint32_t ch; /* unicode character */
+    int32_t w;
+    int32_t h;
+    int32_t x;
+    int32_t y;
+};
+
+/* Error codes returned by tb_init(). All of them are self-explanatory, except
+ * the pipe trap error. Termbox uses unix pipes in order to deliver a message
+ * from a signal handler (SIGWINCH) to the main event reading loop. Honestly in
+ * most cases you should just check the returned code as < 0.
+ */
+#define TB_EUNSUPPORTED_TERMINAL -1
+#define TB_EFAILED_TO_OPEN_TTY   -2
+#define TB_EPIPE_TRAP_ERROR      -3
+
+/* Flags passed to tb_init_with() to specify which features should be enabled.
+ */
+#define TB_INIT_ALTSCREEN   1
+#define TB_INIT_KEYPAD      2
+#define TB_INIT_EVERYTHING -1
+
+/* Initializes the termbox library. This function should be called before any
+ * other functions. After successful initialization, the library must be
+ * finalized using the tb_shutdown() function.
+ */
+SO_IMPORT int tb_init(void);
+SO_IMPORT int tb_init_with(int flags);
+SO_IMPORT void tb_shutdown(void);
+
+/* Returns the size of the internal back buffer (which is the same as
+ * terminal's window size in characters). The internal buffer can be resized
+ * after tb_clear() or tb_present() function calls. Both dimensions have an
+ * unspecified negative value when called before tb_init() or after
+ * tb_shutdown().
+ */
+SO_IMPORT int tb_width(void);
+SO_IMPORT int tb_height(void);
+
+/* Clears the internal back buffer using TB_DEFAULT color or the
+ * color/attributes set by tb_set_clear_attributes() function.
+ */
+SO_IMPORT void tb_clear(void);
+SO_IMPORT void tb_set_clear_attributes(uint16_t fg, uint16_t bg);
+
+/* Synchronizes the internal back buffer with the terminal. */
+SO_IMPORT void tb_present(void);
+
+#define TB_HIDE_CURSOR -1
+
+/* Sets the position of the cursor. Upper-left character is (0, 0). If you pass
+ * TB_HIDE_CURSOR as both coordinates, then the cursor will be hidden. Cursor
+ * is hidden by default.
+ */
+SO_IMPORT void tb_set_cursor(int cx, int cy);
+
+/* Changes cell's parameters in the internal back buffer at the specified
+ * position.
+ */
+SO_IMPORT void tb_put_cell(int x, int y, const struct tb_cell *cell);
+SO_IMPORT void tb_change_cell(int x, int y, uint32_t ch, uint16_t fg, uint16_t bg);
+
+/* Copies the buffer from 'cells' at the specified position, assuming the
+ * buffer is a two-dimensional array of size ('w' x 'h'), represented as a
+ * one-dimensional buffer containing lines of cells starting from the top.
+ *
+ * (DEPRECATED: use tb_cell_buffer() instead and copy memory on your own)
+ */
+SO_IMPORT void tb_blit(int x, int y, int w, int h, const struct tb_cell *cells);
+
+/* Returns a pointer to internal cell back buffer. You can get its dimensions
+ * using tb_width() and tb_height() functions. The pointer stays valid as long
+ * as no tb_clear() and tb_present() calls are made. The buffer is
+ * one-dimensional buffer containing lines of cells starting from the top.
+ */
+SO_IMPORT struct tb_cell *tb_cell_buffer(void);
+
+#define TB_INPUT_CURRENT 0 /* 000 */
+#define TB_INPUT_ESC     1 /* 001 */
+#define TB_INPUT_ALT     2 /* 010 */
+#define TB_INPUT_MOUSE   4 /* 100 */
+
+/* Sets the termbox input mode. Termbox has two input modes:
+ * 1. Esc input mode.
+ *    When ESC sequence is in the buffer and it doesn't match any known
+ *    ESC sequence => ESC means TB_KEY_ESC.
+ * 2. Alt input mode.
+ *    When ESC sequence is in the buffer and it doesn't match any known
+ *    sequence => ESC enables TB_MOD_ALT modifier for the next keyboard event.
+ *
+ * You can also apply TB_INPUT_MOUSE via bitwise OR operation to either of the
+ * modes (e.g. TB_INPUT_ESC | TB_INPUT_MOUSE). If none of the main two modes
+ * were set, but the mouse mode was, TB_INPUT_ESC mode is used. If for some
+ * reason you've decided to use (TB_INPUT_ESC | TB_INPUT_ALT) combination, it
+ * will behave as if only TB_INPUT_ESC was selected.
+ *
+ * If 'mode' is TB_INPUT_CURRENT, it returns the current input mode.
+ *
+ * Default termbox input mode is TB_INPUT_ESC.
+ */
+SO_IMPORT int tb_select_input_mode(int mode);
+
+#define TB_OUTPUT_CURRENT   0
+#define TB_OUTPUT_NORMAL    1
+#define TB_OUTPUT_256       2
+#define TB_OUTPUT_216       3
+#define TB_OUTPUT_GRAYSCALE 4
+
+/* Sets the termbox output mode. Termbox has three output options:
+ * 1. TB_OUTPUT_NORMAL     => [1..8]
+ *    This mode provides 8 different colors:
+ *      black, red, green, yellow, blue, magenta, cyan, white
+ *    Shortcut: TB_BLACK, TB_RED, ...
+ *    Attributes: TB_BOLD, TB_UNDERLINE, TB_REVERSE
+ *
+ *    Example usage:
+ *        tb_change_cell(x, y, '@', TB_BLACK | TB_BOLD, TB_RED);
+ *
+ * 2. TB_OUTPUT_256        => [0..256]
+ *    In this mode you can leverage the 256 terminal mode:
+ *    0x00 - 0x07: the 8 colors as in TB_OUTPUT_NORMAL
+ *    0x08 - 0x0f: TB_* | TB_BOLD
+ *    0x10 - 0xe7: 216 different colors
+ *    0xe8 - 0xff: 24 different shades of grey
+ *
+ *    Example usage:
+ *        tb_change_cell(x, y, '@', 184, 240);
+ *        tb_change_cell(x, y, '@', 0xb8, 0xf0);
+ *
+ * 2. TB_OUTPUT_216        => [0..216]
+ *    This mode supports the 3rd range of the 256 mode only.
+ *    But you don't need to provide an offset.
+ *
+ * 3. TB_OUTPUT_GRAYSCALE  => [0..23]
+ *    This mode supports the 4th range of the 256 mode only.
+ *    But you dont need to provide an offset.
+ *
+ * Execute build/src/demo/output to see its impact on your terminal.
+ *
+ * If 'mode' is TB_OUTPUT_CURRENT, it returns the current output mode.
+ *
+ * Default termbox output mode is TB_OUTPUT_NORMAL.
+ */
+SO_IMPORT int tb_select_output_mode(int mode);
+
+/* Wait for an event up to 'timeout' milliseconds and fill the 'event'
+ * structure with it, when the event is available. Returns the type of the
+ * event (one of TB_EVENT_* constants) or -1 if there was an error or 0 in case
+ * there were no event during 'timeout' period.
+ */
+SO_IMPORT int tb_peek_event(struct tb_event *event, int timeout);
+
+/* Wait for an event forever and fill the 'event' structure with it, when the
+ * event is available. Returns the type of the event (one of TB_EVENT_*
+ * constants) or -1 if there was an error.
+ */
+SO_IMPORT int tb_poll_event(struct tb_event *event);
+
+/* Utility utf8 functions. */
+#define TB_EOF -1
+SO_IMPORT int tb_utf8_char_length(char c);
+SO_IMPORT int tb_utf8_char_to_unicode(uint32_t *out, const char *c);
+SO_IMPORT int tb_utf8_unicode_to_char(char *out, uint32_t c);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/utf8.c b/utf8.c
new file mode 100644 (file)
index 0000000..0c37dae
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,79 @@
+#include "termbox.h"
+
+static const unsigned char utf8_length[256] = {
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+};
+
+static const unsigned char utf8_mask[6] = {
+       0x7F,
+       0x1F,
+       0x0F,
+       0x07,
+       0x03,
+       0x01
+};
+
+int tb_utf8_char_length(char c)
+{
+       return utf8_length[(unsigned char)c];
+}
+
+int tb_utf8_char_to_unicode(uint32_t *out, const char *c)
+{
+       if (*c == 0)
+               return TB_EOF;
+
+       int i;
+       unsigned char len = tb_utf8_char_length(*c);
+       unsigned char mask = utf8_mask[len-1];
+       uint32_t result = c[0] & mask;
+       for (i = 1; i < len; ++i) {
+               result <<= 6;
+               result |= c[i] & 0x3f;
+       }
+
+       *out = result;
+       return (int)len;
+}
+
+int tb_utf8_unicode_to_char(char *out, uint32_t c)
+{
+       int len = 0;
+       int first;
+       int i;
+
+       if (c < 0x80) {
+               first = 0;
+               len = 1;
+       } else if (c < 0x800) {
+               first = 0xc0;
+               len = 2;
+       } else if (c < 0x10000) {
+               first = 0xe0;
+               len = 3;
+       } else if (c < 0x200000) {
+               first = 0xf0;
+               len = 4;
+       } else if (c < 0x4000000) {
+               first = 0xf8;
+               len = 5;
+       } else {
+               first = 0xfc;
+               len = 6;
+       }
+
+       for (i = len - 1; i > 0; --i) {
+               out[i] = (c & 0x3f) | 0x80;
+               c >>= 6;
+       }
+       out[0] = c | first;
+
+       return len;
+}
diff --git a/vec.h b/vec.h
new file mode 100644 (file)
index 0000000..c7b6113
--- /dev/null
+++ b/vec.h
@@ -0,0 +1,120 @@
+/**
+  @brief Generic vector implementation.
+  @author Michael D. Lowis
+  @license BSD 2-clause License
+*/
+#ifndef VEC_H
+#define VEC_H
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+    size_t   elem_count;
+    size_t   elem_size;
+    size_t   elem_capacity;
+    uint8_t* elem_buffer;
+} vec_t;
+
+typedef int (*vec_cmpfn_t)(const void*,const void*);
+
+#ifndef DEFAULT_VEC_CAPACITY
+#define DEFAULT_VEC_CAPACITY (size_t)8
+#endif
+
+static void vec_init(vec_t* vec, size_t elem_size) {
+    vec->elem_size     = elem_size;
+    vec->elem_count    = 0;
+    vec->elem_capacity = DEFAULT_VEC_CAPACITY;
+    vec->elem_buffer   = malloc(elem_size * vec->elem_capacity);
+}
+
+static size_t vec_size(vec_t* vec) {
+    return vec->elem_count;
+}
+
+static bool vec_empty(vec_t* vec) {
+    return (vec->elem_count == 0);
+}
+
+static size_t vec_capacity(vec_t* vec) {
+    return vec->elem_capacity;
+}
+
+static size_t vec_next_capacity(size_t req_size) {
+    size_t next_power = req_size;
+    size_t num_bits = sizeof(size_t) * 8;
+    size_t bit_n;
+
+    /* Find the next highest power of 2 */
+    next_power--;
+    for (bit_n = 1; bit_n < num_bits; bit_n = bit_n << 1)
+        next_power = next_power | (next_power >> bit_n);
+    next_power++;
+
+    return next_power;
+}
+
+static void vec_reserve(vec_t* vec, size_t size) {
+    vec->elem_buffer   = realloc(vec->elem_buffer, size * vec->elem_size);
+    vec->elem_capacity = size;
+}
+
+static void vec_resize(vec_t* vec, size_t count, void* fillval) {
+    if (count > vec->elem_count) {
+        vec_reserve(vec, vec_next_capacity(count+1));
+        for (; vec->elem_count < count; vec->elem_count++)
+            memcpy(&(vec->elem_buffer[vec->elem_count * vec->elem_size]), fillval, vec->elem_size);
+    } else if (count < vec->elem_count) {
+        vec->elem_count = count;
+    }
+}
+
+static void vec_shrink_to_fit(vec_t* vec) {
+    vec->elem_buffer   = realloc(vec->elem_buffer, vec->elem_count * vec->elem_size);
+    vec->elem_capacity = vec->elem_count;
+}
+
+static void* vec_at(vec_t* vec, size_t index) {
+    return &(vec->elem_buffer[index * vec->elem_size]);
+}
+
+static void vec_set(vec_t* vec, size_t index, void* data) {
+    memcpy(&(vec->elem_buffer[index * vec->elem_size]), data, vec->elem_size);
+}
+
+static bool vec_insert(vec_t* vec, size_t index, size_t num_elements, ...) {
+    (void)vec;
+    (void)index;
+    (void)num_elements;
+    return false;
+}
+
+static bool vec_erase(vec_t* vec, size_t start_idx, size_t end_idx) {
+    (void)vec;
+    (void)start_idx;
+    (void)end_idx;
+    return false;
+}
+
+static void vec_push_back(vec_t* vec, void* data) {
+    vec_resize(vec, vec->elem_count+1, data);
+}
+
+static void vec_pop_back(vec_t* vec, void* outdata) {
+    vec->elem_count--;
+    memcpy(outdata, &(vec->elem_buffer[vec->elem_count * vec->elem_size]), vec->elem_size);
+}
+
+static void vec_clear(vec_t* vec) {
+    vec->elem_count = 0;
+}
+
+static void vec_sort(vec_t* vec, int (*cmpfn)(const void*,const void*)) {
+    qsort(vec->elem_buffer, vec->elem_count, vec->elem_size, cmpfn);
+}
+
+#endif /* VEC_H */