URI: 
       remove overly generic code - iomenu - interactive terminal-based selection menu
  HTML git clone git://bitreich.org/iomenu git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/iomenu
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
   DIR commit 774936ea25643995d42c2ca1cd7052875fc35573
   DIR parent f099742c20929af4c304bb477ad8ec42e0bb6f17
  HTML Author: Josuah Demangeon <me@josuah.net>
       Date:   Tue, 27 Oct 2020 22:34:48 +0100
       
       remove overly generic code
       
       Generic library-style code is good for larger projects that have an interest
       at putting in common a lot of code.
       
       Narrowing the scope to just the problem to solve makes the implementation simpler.
       
       Diffstat:
         M Makefile                            |       9 ++++-----
         A compat.c                            |      59 +++++++++++++++++++++++++++++++
         R src/compat.h -> compat.h            |       0 
         M iomenu.c                            |     129 +++++++++++++++++++------------
         D src/compat/strcasestr.c             |      25 -------------------------
         D src/compat/strlcpy.c                |      15 ---------------
         D src/compat/strsep.c                 |      23 -----------------------
         D src/log.c                           |      72 -------------------------------
         D src/log.h                           |      14 --------------
         D src/mem.c                           |     160 -------------------------------
         D src/mem.h                           |      59 -------------------------------
         D src/term.c                          |     107 -------------------------------
         D src/term.h                          |      39 -------------------------------
         A term.c                              |     107 +++++++++++++++++++++++++++++++
         A term.h                              |      38 +++++++++++++++++++++++++++++++
         R src/utf8.c -> utf8.c                |       0 
         R src/utf8.h -> utf8.h                |       0 
         R src/compat/wcwidth.c -> wcwidth.c   |       0 
         A wcwidth.h                           |      13 +++++++++++++
       
       19 files changed, 299 insertions(+), 570 deletions(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       @@ -1,12 +1,11 @@
        NAME = iomenu
        VERSION = 0.1
        
       -SRC = src/utf8.c src/log.c src/mem.c src/compat/strcasestr.c \
       -  src/compat/strsep.c src/compat/strlcpy.c src/compat/wcwidth.c src/term.c
       -HDR = src/mem.h src/compat.h src/log.h src/term.h src/utf8.h
       +SRC = utf8.c compat.c wcwidth.c term.c
       +HDR = utf8.h compat.h wcwidth.h term.h
       +OBJ = ${SRC:.c=.o}
        BIN = iomenu
        MAN1 = ${BIN:=.1}
       -OBJ = ${SRC:.c=.o}
        LIB =
        
        W = -Wall -Wextra -std=c99 --pedantic
       @@ -28,7 +27,7 @@ ${BIN}: ${OBJ} ${BIN:=.o}
                ${CC} ${LDFLAGS} -o $@ $@.o ${OBJ} ${LIB}
        
        clean:
       -        rm -rf *.o */*.o ${BIN} ${NAME}-${VERSION} *.gz
       +        rm -rf *.o ${BIN} ${NAME}-${VERSION} *.gz
        
        install:
                mkdir -p ${DESTDIR}${PREFIX}/bin
   DIR diff --git a/compat.c b/compat.c
       @@ -0,0 +1,59 @@
       +#include <ctype.h>
       +#include <stddef.h>
       +
       +#include <string.h>
       +
       +#include "compat.h"
       +
       +char *
       +strcasestr(const char *str1, const char *str2)
       +{
       +        const char *s1;
       +        const char *s2;
       +
       +        for (;;) {
       +                s1 = str1;
       +                s2 = str2;
       +                while (*s1 != '\0' && tolower(*s1) == tolower(*s2))
       +                        s1++, s2++;
       +                if (*s2 == '\0')
       +                        return (char *) str1;
       +                if (*s1 == '\0')
       +                        return NULL;
       +                str1++;
       +        }
       +
       +        return NULL;
       +}
       +
       +size_t
       +strlcpy(char *buf, char const *str, size_t sz)
       +{
       +        size_t len, cpy;
       +
       +        len = strlen(str);
       +        cpy = (len > sz) ? (sz) : (len);
       +        memcpy(buf, str, cpy + 1);
       +        buf[sz - 1] = '\0';
       +        return len;
       +}
       +
       +char *
       +strsep(char **str_p, char const *sep)
       +{
       +        char *s, *prev;
       +
       +        if (*str_p == NULL)
       +                return NULL;
       +
       +        for (s = prev = *str_p; strchr(sep, *s) == NULL; s++)
       +                continue;
       +
       +        if (*s == '\0') {
       +                *str_p = NULL;
       +        } else {
       +                *s = '\0';
       +                *str_p = s + 1;
       +        }
       +        return prev;
       +}
   DIR diff --git a/src/compat.h b/compat.h
   DIR diff --git a/iomenu.c b/iomenu.c
       @@ -10,23 +10,21 @@
        #include <sys/ioctl.h>
        #include <termios.h>
        #include <unistd.h>
       +#include <assert.h>
        
        #include "compat.h"
       -#include "log.h"
       -#include "mem.h"
        #include "term.h"
        #include "utf8.h"
        
        struct {
       -        FILE *tty;
                char input[LINE_MAX];
                size_t cur;
        
                char **lines_buf;
       -        size_t lines_len;
       +        size_t lines_count;
        
                char **match_buf;
       -        size_t match_len;
       +        size_t match_count;
        } ctx;
        
        int opt_comment;
       @@ -51,20 +49,44 @@ match_line(char *line, char **tokv)
         * error message.
         */
        static void
       -goodbye(const char *s)
       +die(const char *msg)
        {
                int e = errno;
        
                term_raw_off(2);
       +
       +        fprintf(stderr, "iomenu: ");
                errno = e;
       -        die("%s", s);
       +        perror(msg);
       +
       +        exit(1);
       +}
       +
       +void *
       +xrealloc(void *ptr, size_t sz)
       +{
       +        ptr = realloc(ptr, sz);
       +        if (ptr == NULL)
       +                die("realloc");
       +        return ptr;
       +}
       +
       +void *
       +xmalloc(size_t sz)
       +{
       +        void *ptr;
       +
       +        ptr = malloc(sz);
       +        if (ptr == NULL)
       +                die("malloc");
       +        return ptr;
        }
        
        static void
        do_move(int sign)
        {
                /* integer overflow will do what we need */
       -        for (size_t i = ctx.cur + sign; i < ctx.match_len; i += sign) {
       +        for (size_t i = ctx.cur + sign; i < ctx.match_count; i += sign) {
                        if (opt_comment == 0 || ctx.match_buf[i][0] != '#') {
                                ctx.cur = i;
                                break;
       @@ -78,7 +100,7 @@ do_move(int sign)
         * of `searchv' of size `searchc'
         */
        static void
       -do_filter(char **search_buf, size_t search_len)
       +do_filter(char **search_buf, size_t search_count)
        {
                char **t, *tokv[(sizeof ctx.input + 1) * sizeof(char *)];
                char *b, buf[sizeof ctx.input];
       @@ -89,10 +111,10 @@ do_filter(char **search_buf, size_t search_len)
                        continue;
                *t = NULL;
        
       -        ctx.cur = ctx.match_len = 0;
       -        for (size_t n = 0; n < search_len; n++)
       +        ctx.cur = ctx.match_count = 0;
       +        for (size_t n = 0; n < search_count; n++)
                        if (match_line(search_buf[n], tokv))
       -                        ctx.match_buf[ctx.match_len++] = search_buf[n];
       +                        ctx.match_buf[ctx.match_count++] = search_buf[n];
                if (opt_comment && ctx.match_buf[ctx.cur][0] == '#')
                        do_move(+1);
        }
       @@ -103,7 +125,7 @@ do_move_page(signed int sign)
                int rows = term.winsize.ws_row - 1;
                size_t i = ctx.cur - ctx.cur % rows + rows * sign;
        
       -        if (i >= ctx.match_len)
       +        if (i >= ctx.match_count)
                        return;
                ctx.cur = i - 1;
        
       @@ -120,7 +142,7 @@ do_move_header(signed int sign)
                for (ctx.cur += sign;; ctx.cur += sign) {
                        char *cur = ctx.match_buf[ctx.cur];
        
       -                if (ctx.cur >= ctx.match_len) {
       +                if (ctx.cur >= ctx.match_count) {
                                ctx.cur--;
                                break;
                        }
       @@ -142,7 +164,7 @@ do_remove_word(void)
                len = strlen(ctx.input) - 1;
                for (i = len; i >= 0 && !isspace(ctx.input[i]); i--)
                        ctx.input[i] = '\0';
       -        do_filter(ctx.lines_buf, ctx.lines_len);
       +        do_filter(ctx.lines_buf, ctx.lines_count);
        }
        
        static void
       @@ -157,7 +179,7 @@ do_add_char(char c)
                        ctx.input[len] = c;
                        ctx.input[len + 1] = '\0';
                }
       -        do_filter(ctx.match_buf, ctx.match_len);
       +        do_filter(ctx.match_buf, ctx.match_count);
        }
        
        static void
       @@ -175,7 +197,7 @@ do_print_selection(void)
                        fprintf(stdout, "%c", '\t');
                }
                term_raw_off(2);
       -        if (ctx.match_len == 0
       +        if (ctx.match_count == 0
                  || (opt_comment && ctx.match_buf[ctx.cur][0] == '#'))
                        fprintf(stdout, "%s\n", ctx.input);
                else
       @@ -204,7 +226,7 @@ key_action(void)
                        return -1;
                case TERM_KEY_CTRL('U'):
                        ctx.input[0] = '\0';
       -                do_filter(ctx.lines_buf, ctx.lines_len);
       +                do_filter(ctx.lines_buf, ctx.lines_count);
                        break;
                case TERM_KEY_CTRL('W'):
                        do_remove_word();
       @@ -212,7 +234,7 @@ key_action(void)
                case TERM_KEY_DELETE:
                case TERM_KEY_BACKSPACE:
                        ctx.input[strlen(ctx.input) - 1] = '\0';
       -                do_filter(ctx.lines_buf, ctx.lines_len);
       +                do_filter(ctx.lines_buf, ctx.lines_count);
                        break;
                case TERM_KEY_ARROW_UP:
                case TERM_KEY_CTRL('P'):
       @@ -237,10 +259,10 @@ key_action(void)
                        do_move_page(+1);
                        break;
                case TERM_KEY_TAB:
       -                if (ctx.match_len == 0)
       +                if (ctx.match_count == 0)
                                break;
                        strlcpy(ctx.input, ctx.match_buf[ctx.cur], sizeof(ctx.input));
       -                do_filter(ctx.match_buf, ctx.match_len);
       +                do_filter(ctx.match_buf, ctx.match_count);
                        break;
                case TERM_KEY_ENTER:
                case TERM_KEY_CTRL('M'):
       @@ -281,7 +303,7 @@ do_print_screen(void)
                i = ctx.cur - ctx.cur % rows;
                m = ctx.match_buf + i;
                fprintf(stderr, "\x1b[2J");
       -        while (p < rows && i < ctx.match_len) {
       +        while (p < rows && i < ctx.match_count) {
                        print_line(*m, i == ctx.cur);
                        p++, i++, m++;
                }
       @@ -294,7 +316,7 @@ static void
        sig_winch(int sig)
        {
                if (ioctl(STDERR_FILENO, TIOCGWINSZ, &term.winsize) == -1)
       -                goodbye("ioctl");
       +                die("ioctl");
                do_print_screen();
                signal(sig, sig_winch);
        }
       @@ -306,15 +328,25 @@ usage(char const *arg0)
                exit(1);
        }
        
       -static void
       -read_stdin(char **buf, struct mem_pool *pool)
       +static int
       +read_stdin(char **buf)
        {
       -        if (mem_read((void **)buf, pool) < 0)
       -                goodbye("reading standard input");
       -        if (memchr(*buf, '\0', mem_length(*buf)) != NULL)
       -                goodbye("'\\0' byte in input");
       -        if (mem_append((void **)buf, "", 1) < 0)
       -                goodbye("adding '\\0' terminator");
       +        size_t len = 0;
       +
       +        assert(*buf == NULL);
       +
       +        for (int c; (c = fgetc(stdin)) != EOF;) {
       +                if (c == '\0') {
       +                        fprintf(stderr, "iomenu: ignoring '\\0' byte in input\r\n");
       +                        continue;
       +                }
       +                *buf = xrealloc(*buf, sizeof *buf + len + 1);
       +                (*buf)[len++] = c;
       +        }
       +        *buf = xrealloc(*buf, sizeof *buf + len + 1);
       +        (*buf)[len] = '\0';
       +
       +        return 0;
        }
        
        /*
       @@ -322,27 +354,24 @@ read_stdin(char **buf, struct mem_pool *pool)
         * line, but using the input buffer and replacing '\n' by '\0'.
         */
        static void
       -split_lines(char *s, struct mem_pool *pool)
       +split_lines(char *s)
        {
       -        ctx.lines_buf = mem_alloc(pool, 0);
       -        if (ctx.lines_buf == NULL)
       -                goodbye("initializing full lines buffer");
       +        size_t sz;
        
       -        ctx.lines_len = 0;
       +        ctx.lines_count = 0;
                for (;;) {
       -                if (mem_append((void **)&ctx.lines_buf, &s, sizeof s) < 0)
       -                        goodbye("adding line to array");
       -                ctx.lines_len++;
       +                sz = (ctx.lines_count + 1) * sizeof s;
       +                ctx.lines_buf = xrealloc(ctx.lines_buf, sz);
       +                ctx.lines_buf[ctx.lines_count++] = s;
        
                        s = strchr(s, '\n');
                        if (s == NULL)
                                break;
                        *s++ = '\0';
                }
       -
       -        ctx.match_buf = mem_alloc(pool, mem_length(ctx.lines_buf));
       -        if (ctx.match_buf == NULL)
       -                goodbye("initializing matching lines buffer");
       +        sz = ctx.lines_count * sizeof s;
       +        ctx.match_buf = xmalloc(sz);
       +        memcpy(ctx.match_buf, ctx.lines_buf, sz);
        }
        
        /*
       @@ -353,8 +382,7 @@ split_lines(char *s, struct mem_pool *pool)
        int
        main(int argc, char *argv[])
        {
       -        struct mem_pool pool = {0};
       -        char *buf;
       +        char *buf = NULL, *arg0;
        
                arg0 = *argv;
                for (int opt; (opt = getopt(argc, argv, "#v")) > 0;) {
       @@ -372,17 +400,17 @@ main(int argc, char *argv[])
                argc -= optind;
                argv += optind;
        
       -        read_stdin(&buf, &pool);
       -        split_lines(buf, &pool);
       +        read_stdin(&buf);
       +        split_lines(buf);
        
       -        do_filter(ctx.lines_buf, ctx.lines_len);
       +        do_filter(ctx.lines_buf, ctx.lines_count);
        
                if (!isatty(2))
       -                goodbye("file descriptor 2 (stderr)");
       +                die("file descriptor 2 (stderr)");
        
                freopen("/dev/tty", "w+", stderr);
                if (stderr == NULL)
       -                goodbye("re-opening standard error read/write");
       +                die("re-opening standard error read/write");
        
                term_raw_on(2);
                sig_winch(SIGWINCH);
       @@ -396,6 +424,5 @@ main(int argc, char *argv[])
        
                term_raw_off(2);
        
       -        mem_free(&pool);
                return 0;
        }
   DIR diff --git a/src/compat/strcasestr.c b/src/compat/strcasestr.c
       @@ -1,25 +0,0 @@
       -#include <ctype.h>
       -#include <stddef.h>
       -
       -#include "compat.h"
       -
       -char *
       -strcasestr(const char *str1, const char *str2)
       -{
       -        const char *s1;
       -        const char *s2;
       -
       -        for (;;) {
       -                s1 = str1;
       -                s2 = str2;
       -                while (*s1 != '\0' && tolower(*s1) == tolower(*s2))
       -                        s1++, s2++;
       -                if (*s2 == '\0')
       -                        return (char *) str1;
       -                if (*s1 == '\0')
       -                        return NULL;
       -                str1++;
       -        }
       -
       -        return NULL;
       -}
   DIR diff --git a/src/compat/strlcpy.c b/src/compat/strlcpy.c
       @@ -1,15 +0,0 @@
       -#include "compat.h"
       -
       -#include <string.h>
       -
       -size_t
       -strlcpy(char *buf, char const *str, size_t sz)
       -{
       -        size_t len, cpy;
       -
       -        len = strlen(str);
       -        cpy = (len > sz) ? (sz) : (len);
       -        memcpy(buf, str, cpy + 1);
       -        buf[sz - 1] = '\0';
       -        return len;
       -}
   DIR diff --git a/src/compat/strsep.c b/src/compat/strsep.c
       @@ -1,23 +0,0 @@
       -#include <string.h>
       -
       -#include "compat.h"
       -
       -char *
       -strsep(char **str_p, char const *sep)
       -{
       -        char *s, *prev;
       -
       -        if (*str_p == NULL)
       -                return NULL;
       -
       -        for (s = prev = *str_p; strchr(sep, *s) == NULL; s++)
       -                continue;
       -
       -        if (*s == '\0') {
       -                *str_p = NULL;
       -        } else {
       -                *s = '\0';
       -                *str_p = s + 1;
       -        }
       -        return prev;
       -}
   DIR diff --git a/src/log.c b/src/log.c
       @@ -1,72 +0,0 @@
       -#include "log.h"
       -
       -#include <assert.h>
       -#include <errno.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -
       -#ifndef LOG_DEFAULT
       -#define LOG_DEFAULT 3 /* info */
       -#endif
       -
       -char *arg0 = NULL;
       -static int log_level = -1;
       -
       -void
       -log_vprintf(int level, char const *flag, char const *fmt, va_list va)
       -{
       -        char *env;
       -        int old_errno = errno;
       -
       -        if (log_level < 0) {
       -                env = getenv("LOG");
       -                log_level = (env == NULL) ? 0 : atoi(env);
       -                if (log_level == 0)
       -                        log_level = LOG_DEFAULT;
       -        }
       -
       -        if (log_level < level)
       -                return;
       -
       -        if (arg0 != NULL)
       -                fprintf(stderr, "%s: ", arg0);
       -
       -        fprintf(stderr, "%s: ", flag);
       -        vfprintf(stderr, fmt, va);
       -
       -        if (old_errno != 0)
       -                fprintf(stderr, ": %s", strerror(old_errno));
       -
       -        fprintf(stderr, "\n");
       -        fflush(stderr);
       -}
       -
       -void
       -die(char const *fmt, ...)
       -{
       -        va_list va;
       -        va_start(va, fmt); log_vprintf(1, "error", fmt, va); va_end(va);
       -        exit(1);
       -}
       -
       -void
       -warn(char const *fmt, ...)
       -{
       -        va_list va;
       -        va_start(va, fmt); log_vprintf(2, "warn", fmt, va); va_end(va);
       -}
       -
       -void
       -info(char const *fmt, ...)
       -{
       -        va_list va;
       -        va_start(va, fmt); log_vprintf(3, "info", fmt, va); va_end(va);
       -}
       -
       -void
       -debug(char const *fmt, ...)
       -{
       -        va_list va;
       -        va_start(va, fmt); log_vprintf(4, "debug", fmt, va); va_end(va);
       -}
   DIR diff --git a/src/log.h b/src/log.h
       @@ -1,14 +0,0 @@
       -#ifndef LOG_H
       -#define LOG_H
       -
       -#include <stdarg.h>
       -
       -/** src/log.c **/
       -char *arg0;
       -void log_vprintf(int level, char const *flag, char const *fmt, va_list va);
       -void die(char const *fmt, ...);
       -void warn(char const *fmt, ...);
       -void info(char const *fmt, ...);
       -void debug(char const *fmt, ...);
       -
       -#endif
   DIR diff --git a/src/mem.c b/src/mem.c
       @@ -1,160 +0,0 @@
       -#include "mem.h"
       -
       -#include <assert.h>
       -#include <errno.h>
       -#include <stdint.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <unistd.h>
       -
       -static struct mem_block *
       -mem_block(void **memp)
       -{
       -        struct mem_block *block = (void *)((char *)memp - sizeof *block);
       -
       -        assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
       -        return block;
       -}
       -
       -void *
       -mem_alloc(struct mem_pool *pool, size_t len)
       -{
       -        struct mem_block *block;
       -
       -        block = calloc(1, sizeof *block + len);
       -        if (block == NULL)
       -                return NULL;
       -        memcpy(block->magic, MEM_BLOCK_MAGIC, 8);
       -
       -        block->len = len;
       -        block->pool = pool;
       -        block->prev = NULL;
       -        block->next = pool->head;
       -        if (pool->head != NULL)
       -                pool->head->prev = block;
       -        pool->head = block;
       -
       -        return block->buf;
       -}
       -
       -int
       -mem_resize(void **memp, size_t len)
       -{
       -        struct mem_block *block = mem_block(*memp);
       -        int is_first = (block == block->pool->head);
       -        int is_same;
       -        void *v;
       -
       -        v = realloc(block, sizeof *block + len);
       -        if (v == NULL)
       -                return -1;
       -        is_same = (block == v);
       -        block = v;
       -
       -        block->len = len;
       -
       -        if (is_same)
       -                return 0;
       -
       -        if (block->prev != NULL)
       -                block->prev->next = v;
       -        if (block->next != NULL)
       -                block->next->prev = v;
       -        if (is_first)
       -                block->pool->head = v;
       -        *memp = block->buf;
       -
       -        assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
       -        return 0;
       -}
       -
       -int
       -mem_grow(void **memp, size_t len)
       -{
       -        assert(SIZE_MAX - len >= mem_block(*memp)->len);
       -
       -        return mem_resize(memp, mem_length(*memp) + len);
       -}
       -
       -int
       -mem_shrink(void **memp, size_t len)
       -{
       -        assert(mem_block(*memp)->len >= len);
       -
       -        return mem_resize(memp, mem_length(*memp) - len);
       -}
       -
       -size_t
       -mem_length(void *mem)
       -{
       -        return mem_block(mem)->len;
       -}
       -
       -int
       -mem_append(void **memp, void const *buf, size_t len)
       -{
       -        size_t old_len = mem_length(*memp);
       -        struct mem_block *block;
       -
       -        if (mem_grow(memp, len) < 0)
       -                return -1;
       -        block = mem_block(*memp);
       -        memcpy((char *)block->buf + old_len, buf, len);
       -
       -        assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
       -        return 0;
       -}
       -
       -int
       -mem_read(void **memp, struct mem_pool *pool)
       -{
       -        struct mem_block *block;
       -        ssize_t sz = 0;
       -        void *mem;
       -
       -        mem = mem_alloc(pool, 0);
       -        if (mem == NULL)
       -                return -1;
       -
       -        for (ssize_t r = 1; r > 0; sz += r) {
       -                if (mem_resize(&mem, sz + 2048) < 0)
       -                        return -1;
       -
       -                r = read(0, (char *)mem + sz, 2048);
       -                if (r < 0)
       -                        return -1;
       -        }
       -        block = mem_block(mem);
       -        block->len = sz;
       -
       -        *memp = mem;
       -        assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
       -        return 0;
       -}
       -
       -void
       -mem_delete(void *mem)
       -{
       -        struct mem_block *block = mem_block(mem);;
       -
       -        if (block == block->pool->head)
       -                block->pool->head = block->next;
       -        if (block->next != NULL)
       -                block->next->prev = block->prev;
       -        if (block->prev != NULL)
       -                block->prev->next = block->next;
       -        memset(block, 0, sizeof *block);
       -        free(block);
       -}
       -
       -void
       -mem_free(struct mem_pool *pool)
       -{
       -        struct mem_block *block, *next;
       -
       -        for (block = pool->head; block != NULL; block = next) {
       -                next = block->next;
       -                memset(block, 0, sizeof *block);
       -                free(block);
       -        }
       -}
   DIR diff --git a/src/mem.h b/src/mem.h
       @@ -1,59 +0,0 @@
       -#ifndef MEM_H
       -#define MEM_H
       -
       -/*
       - * Lightweight wrapper over malloc, that permit to define a memory pool of
       - * multiple buffers, and free them all at once.
       - *
       - *        *──────────┐   
       - *        │ mem_pool │   
       - *        ├──────────┤   
       - *        │*head     │   
       - *        └┬─────────┘   
       - *         v
       - * NULL<   *───────────┐<  >*───────────┐<  >*───────────┐<  >*───────────┐   >NULL
       - *      \  │ mem_block │ \/ │ mem_block │ \/ │ mem_block │ \/ │ mem_block │  /
       - *       \ ├───────────┤ /\ ├───────────┤ /\ ├───────────┤ /\ ├───────────┤ /
       - *        `┤*prev *next├'  `┤*prev *next├'  `┤*prev *next├'  `┤*prev *next├'
       - *         │len        │    │len        │    │len        │    │len        │
       - *         ├─┴─magic───┤    ├─┴─magic───┤    ├─┴─magic───┤    ├─┴─magic───┤
       - *         │///////////│    │///////////│    │///////////│    │///////////│
       - *         │///////////│    │///////////│    │///////////│    │///////////│
       - *         │///////////│    │///////////│    │///////////│    └───────────┘
       - *         └───────────┘    │///////////│    │///////////│
       - *                          │///////////│    └───────────┘
       - *                          └───────────┘
       - *
       - * This permits the type checker to still work on all operations while
       - * providing generic memory management functions for all types of data
       - * structures and keep track of each object's length.
       - */
       -
       -#include <stddef.h>
       -
       -#define MEM_BLOCK_MAGIC "\xcc\x68\x23\xd7\x9b\x7d\x39\xb9"
       -
       -struct mem_pool {
       -        struct mem_block *head;
       -};
       -
       -struct mem_block {
       -        struct mem_pool *pool;
       -        struct mem_block *prev, *next;
       -        size_t len;
       -        char magic[8]; /* at the end to detect buffer underflow */
       -        char buf[];
       -};
       -
       -/** src/mem.c **/
       -void * mem_alloc(struct mem_pool *pool, size_t len);
       -int mem_resize(void **memp, size_t len);
       -int mem_grow(void **memp, size_t len);
       -int mem_shrink(void **memp, size_t len);
       -size_t mem_length(void *mem);
       -int mem_append(void **memp, void const *buf, size_t len);
       -int mem_read(void **memp, struct mem_pool *pool);
       -void mem_delete(void *mem);
       -void mem_free(struct mem_pool *pool);
       -
       -#endif
   DIR diff --git a/src/term.c b/src/term.c
       @@ -1,107 +0,0 @@
       -#include "term.h"
       -
       -#include <ctype.h>
       -#include <stdint.h>
       -#include <stdio.h>
       -#include <string.h>
       -#include <sys/ioctl.h>
       -#include <termios.h>
       -
       -#include "compat.h"
       -#include "utf8.h"
       -
       -struct term term = {0};
       -
       -static int
       -term_codepoint_width(uint32_t codepoint, int pos)
       -{
       -        if (codepoint == '\t')
       -                return 8 - pos % 8;
       -        return wcwidth(codepoint);
       -}
       -
       -int
       -term_at_width(char const *s, int width, int pos)
       -{
       -        char const *beg = s;
       -
       -        for (uint32_t state = 0, codepoint; *s != '\0'; s++) {
       -                if (utf8_decode(&state, &codepoint, *s) == UTF8_ACCEPT) {
       -                        pos += term_codepoint_width(codepoint, pos);
       -                        if (pos > width)
       -                                break;
       -                }
       -        }
       -        return s - beg;
       -}
       -
       -int
       -term_raw_on(int fd)
       -{
       -        struct termios new_termios = {0};
       -        char *seq = "\x1b[s\x1b[?1049h\x1b[H";
       -        ssize_t len = strlen(seq);
       -
       -        if (write(fd, seq, len) < len)
       -                return -1;
       -
       -        if (tcgetattr(fd, &term.old_termios) < 0)
       -                return -1;
       -        memcpy(&new_termios, &term.old_termios, sizeof new_termios);
       -
       -        new_termios.c_lflag &= ~(ICANON | ECHO | IEXTEN | IGNBRK | ISIG);
       -        if (tcsetattr(fd, TCSANOW, &new_termios) == -1)
       -                return -1;
       -        return 0;
       -}
       -
       -int
       -term_raw_off(int fd)
       -{
       -        char *seq = "\x1b[2J\x1b[u\033[?1049l";
       -        ssize_t len = strlen(seq);
       -
       -        if (tcsetattr(fd, TCSANOW, &term.old_termios) < 0)
       -                return -1;
       -        if (write(fd, seq, len) < len)
       -                return -1;
       -        return 0;
       -}
       -
       -int
       -term_get_key(FILE *fp)
       -{
       -        int key, num;
       -
       -        key = fgetc(fp);
       -top:
       -        switch (key) {
       -        case EOF:
       -                return -1;
       -        case TERM_KEY_ALT('['):
       -                key = getc(fp);
       -                if (key == EOF)
       -                        return -1;
       -
       -                for (num = 0; isdigit(key);) {
       -                        num *= 10;
       -                        num += key - '0';
       -
       -                        key = fgetc(fp);
       -                        if (key == EOF)
       -                                return -1;
       -                }
       -
       -                key = TERM_KEY_CSI(key, num);
       -
       -                goto top;
       -        case TERM_KEY_ESC:
       -                key = getc(fp);
       -                if (key == EOF)
       -                        return -1;
       -                key = TERM_KEY_ALT(key);
       -                goto top;
       -        default:
       -                return key;
       -        }
       -}
   DIR diff --git a/src/term.h b/src/term.h
       @@ -1,39 +0,0 @@
       -#ifndef TERM_H
       -#define TERM_H
       -
       -#include <stdint.h>
       -#include <stdio.h>
       -#include <sys/ioctl.h>
       -#include <termios.h>
       -#include <unistd.h>
       -
       -#define TERM_KEY_CTRL(c)    ((c) & ~0x40)
       -#define TERM_KEY_ALT(c)            (0x100 + (c))
       -#define TERM_KEY_CSI(c, i)  (0x100 + (c) * 0x100 + (i))
       -
       -enum term_key {
       -        TERM_KEY_ESC        = 0x1b,
       -        TERM_KEY_DELETE     = 127,
       -        TERM_KEY_BACKSPACE  = TERM_KEY_CTRL('H'),
       -        TERM_KEY_TAB        = TERM_KEY_CTRL('I'),
       -        TERM_KEY_ENTER      = TERM_KEY_CTRL('J'),
       -        TERM_KEY_ARROW_UP   = TERM_KEY_CSI('A', 0),
       -        TERM_KEY_ARROW_DOWN = TERM_KEY_CSI('B', 0),
       -        TERM_KEY_PAGE_UP    = TERM_KEY_CSI('~', 5),
       -        TERM_KEY_PAGE_DOWN  = TERM_KEY_CSI('~', 6),
       -};
       -
       -struct term {
       -        struct winsize winsize;
       -        struct termios old_termios;
       -};
       -
       -/** src/term.c **/
       -struct term term;
       -int term_width_at_pos(uint32_t codepoint, int pos);
       -int term_at_width(char const *s, int width, int pos);
       -int term_raw_on(int fd);
       -int term_raw_off(int fd);
       -int term_get_key(FILE *fp);
       -
       -#endif
   DIR diff --git a/term.c b/term.c
       @@ -0,0 +1,107 @@
       +#include "term.h"
       +
       +#include <ctype.h>
       +#include <stdint.h>
       +#include <stdio.h>
       +#include <string.h>
       +#include <sys/ioctl.h>
       +#include <termios.h>
       +
       +#include "wcwidth.h"
       +#include "utf8.h"
       +
       +struct term term = {0};
       +
       +static int
       +term_codepoint_width(uint32_t codepoint, int pos)
       +{
       +        if (codepoint == '\t')
       +                return 8 - pos % 8;
       +        return wcwidth(codepoint);
       +}
       +
       +int
       +term_at_width(char const *s, int width, int pos)
       +{
       +        char const *beg = s;
       +
       +        for (uint32_t state = 0, codepoint; *s != '\0'; s++) {
       +                if (utf8_decode(&state, &codepoint, *s) == UTF8_ACCEPT) {
       +                        pos += term_codepoint_width(codepoint, pos);
       +                        if (pos > width)
       +                                break;
       +                }
       +        }
       +        return s - beg;
       +}
       +
       +int
       +term_raw_on(int fd)
       +{
       +        struct termios new_termios = {0};
       +        char *seq = "\x1b[s\x1b[?1049h\x1b[H";
       +        ssize_t len = strlen(seq);
       +
       +        if (write(fd, seq, len) < len)
       +                return -1;
       +
       +        if (tcgetattr(fd, &term.old_termios) < 0)
       +                return -1;
       +        memcpy(&new_termios, &term.old_termios, sizeof new_termios);
       +
       +        new_termios.c_lflag &= ~(ICANON | ECHO | IEXTEN | IGNBRK | ISIG);
       +        if (tcsetattr(fd, TCSANOW, &new_termios) == -1)
       +                return -1;
       +        return 0;
       +}
       +
       +int
       +term_raw_off(int fd)
       +{
       +        char *seq = "\x1b[2J\x1b[u\033[?1049l";
       +        ssize_t len = strlen(seq);
       +
       +        if (tcsetattr(fd, TCSANOW, &term.old_termios) < 0)
       +                return -1;
       +        if (write(fd, seq, len) < len)
       +                return -1;
       +        return 0;
       +}
       +
       +int
       +term_get_key(FILE *fp)
       +{
       +        int key, num;
       +
       +        key = fgetc(fp);
       +top:
       +        switch (key) {
       +        case EOF:
       +                return -1;
       +        case TERM_KEY_ALT('['):
       +                key = getc(fp);
       +                if (key == EOF)
       +                        return -1;
       +
       +                for (num = 0; isdigit(key);) {
       +                        num *= 10;
       +                        num += key - '0';
       +
       +                        key = fgetc(fp);
       +                        if (key == EOF)
       +                                return -1;
       +                }
       +
       +                key = TERM_KEY_CSI(key, num);
       +
       +                goto top;
       +        case TERM_KEY_ESC:
       +                key = getc(fp);
       +                if (key == EOF)
       +                        return -1;
       +                key = TERM_KEY_ALT(key);
       +                goto top;
       +        default:
       +                return key;
       +        }
       +}
   DIR diff --git a/term.h b/term.h
       @@ -0,0 +1,38 @@
       +#ifndef TERM_H
       +#define TERM_H
       +
       +#include <stdint.h>
       +#include <stdio.h>
       +#include <sys/ioctl.h>
       +#include <termios.h>
       +#include <unistd.h>
       +
       +#define TERM_KEY_CTRL(c)    ((c) & ~0x40)
       +#define TERM_KEY_ALT(c)            (0x100 + (c))
       +#define TERM_KEY_CSI(c, i)  (0x100 + (c) * 0x100 + (i))
       +
       +enum term_key {
       +        TERM_KEY_ESC        = 0x1b,
       +        TERM_KEY_DELETE     = 127,
       +        TERM_KEY_BACKSPACE  = TERM_KEY_CTRL('H'),
       +        TERM_KEY_TAB        = TERM_KEY_CTRL('I'),
       +        TERM_KEY_ENTER      = TERM_KEY_CTRL('J'),
       +        TERM_KEY_ARROW_UP   = TERM_KEY_CSI('A', 0),
       +        TERM_KEY_ARROW_DOWN = TERM_KEY_CSI('B', 0),
       +        TERM_KEY_PAGE_UP    = TERM_KEY_CSI('~', 5),
       +        TERM_KEY_PAGE_DOWN  = TERM_KEY_CSI('~', 6),
       +};
       +
       +struct term {
       +        struct winsize winsize;
       +        struct termios old_termios;
       +};
       +
       +struct term term;
       +int term_width_at_pos(uint32_t codepoint, int pos);
       +int term_at_width(char const *s, int width, int pos);
       +int term_raw_on(int fd);
       +int term_raw_off(int fd);
       +int term_get_key(FILE *fp);
       +
       +#endif
   DIR diff --git a/src/utf8.c b/utf8.c
   DIR diff --git a/src/utf8.h b/utf8.h
   DIR diff --git a/src/compat/wcwidth.c b/wcwidth.c
   DIR diff --git a/wcwidth.h b/wcwidth.h
       @@ -0,0 +1,13 @@
       +#ifndef WCWIDTH_H
       +#define WCWIDTH_H
       +
       +#include <wchar.h>
       +
       +#define wcwidth(c) mk_wcwidth_cjk(c)
       +
       +int mk_wcwidth(wchar_t ucs);
       +int mk_wcswidth(const wchar_t *pwcs, size_t n);
       +int mk_wcwidth_cjk(wchar_t ucs);
       +int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n);
       +
       +#endif