refactor to pop general-purpose operations out of the main .c file - iomenu - interactive terminal-based selection menu
 (HTM) git clone git://bitreich.org/iomenu git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/iomenu
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit c9ce7898a2d7b4f171d39b5b8b8f333fe4070c6f
 (DIR) parent 9e523e73c8f395ee7270ed2826efa3f7ffb5908d
 (HTM) Author: Josuah Demangeon <me@josuah.net>
       Date:   Wed, 15 Jul 2020 08:35:35 +0200
       
       refactor to pop general-purpose operations out of the main .c file
       
       This permits to improve quality slightly by isolating the various aspects of
       the code: buffer handling, terminal setup, logging, utf-8 handling, character
       classes.
       
       This permits to fix bugs in all programs at once and have better quality
       codebase buy checking the common code in many different situations.
       
       If abused, this can lead to too much cognitive (and performance) overhead
       because of all the extra structures introduced. So it is important to keep the
       library code just a thin layer over some aspects of stdlib.
       
       Diffstat:
         M Makefile                            |       6 +++---
         D bin/io-mblaze                       |      20 --------------------
         D bin/io-xdg-open                     |      32 -------------------------------
         R bin/io-find -> bin/iomenu-find      |       0 
         R bin/io-fstab -> bin/iomenu-fstab    |       0 
         R bin/io-man -> bin/iomenu-man        |       0 
         R bin/io-net -> bin/iomenu-net        |       0 
         R bin/io-passwd -> bin/iomenu-passwd  |       0 
         R bin/io-ps -> bin/iomenu-ps          |       0 
         D config.mk                           |       4 ----
         M iomenu.c                            |     486 ++++++++++++++-----------------
         M src/compat.h                        |       9 ++++++++-
         A src/compat/strlcpy.c                |      15 +++++++++++++++
         M src/log.c                           |      48 +++++++++++++++++++++----------
         M src/log.h                           |      11 +++++------
         M src/mem.c                           |      70 ++++++++++++++++++++++----------
         M src/mem.h                           |      13 +++++++------
         D src/str.c                           |     119 -------------------------------
         D src/str.h                           |      26 --------------------------
         A src/term.c                          |     107 +++++++++++++++++++++++++++++++
         A src/term.h                          |      39 +++++++++++++++++++++++++++++++
         M src/utf8.c                          |     263 ++++++++-----------------------
         M src/utf8.h                          |      17 ++++++++++-------
         D src/util.h                          |      12 ------------
         D t/test.c                            |      22 ----------------------
       
       25 files changed, 565 insertions(+), 754 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       @@ -1,10 +1,10 @@
        NAME = iomenu
        VERSION = 0.1
        
       -SRC = src/utf8.c src/str.c src/log.c src/mem.c src/compat/strcasestr.c \
       -  src/compat/strsep.c src/compat/wcwidth.c
       +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/util.h src/str.h src/log.h src/utf8.h
       +HDR = src/mem.h src/compat.h src/log.h src/term.h src/utf8.h
        
        BIN = iomenu
        
 (DIR) diff --git a/bin/io-mblaze b/bin/io-mblaze
       @@ -1,20 +0,0 @@
       -#!/bin/sh -e
       -# mail client using iomenu, mblaze, to be used with mfilter
       -
       -cd "$HOME/mail"
       -
       -if test "$1" = -a
       -then        IFS='        ' read dir mail <<EOF
       -$(iomenu -'#' <index)
       -EOF
       -        mseq -S <$dir/seq >/dev/null
       -        exec mless "$(echo $mail | sed -r 's, *([0-9]+).*,\1,')"
       -fi
       -
       -wc -l */seq | sed 's, 0, .,; s,/seq$,,' |
       -        iomenu | sed -r 's,^ +[^ ]+ +,,' |
       -        xargs printf %s/%s "$(pwd)" | mlist | msort -r -d | mseq -S
       -
       -mscan -f '%6n %u %D  %20f %t%2i%120S' |
       -        iomenu | sed -r 's,^ *([0-9]*) .*,\1,' |
       -        xargs mless
 (DIR) diff --git a/bin/io-xdg-open b/bin/io-xdg-open
       @@ -1,32 +0,0 @@
       -#!/bin/sh -e
       -# pick a file to open with xdg-open with iomenu with caching
       -#
       -# The cache is updated when a directory is selected.
       -
       -LC_COLLATE=C
       -
       -touch "$HOME/.cache/find"
       -
       -if        test -f "$HOME/.cache/find" && test $# = 0
       -then        exec "$0" "$HOME"
       -elif        test $# = 0
       -then        exec xdg-open "$(iomenu <$HOME/.cache/find)"
       -fi
       -
       -mkdir -p "$HOME/.cache"
       -{
       -        find "$1" '(' -name .git -o -name CVS ')' -prune -o \
       -                -type d -exec printf '%s/\n' '{}' + -o \
       -                -type f -exec printf '%s\n' '{}' + | tee "$HOME/.cache/$$" 
       -        grep -vF "$1" $HOME/.cache/find
       -} | sort -o "$HOME/.cache/find"
       -
       -s=$(iomenu <$HOME/.cache/$$)
       -
       -rm "$HOME/.cache/$$"
       -
       -case $s in
       -('')        exit 1 ;;
       -(*/)        exec "$0" "$(cd "$s" && pwd)" ;;
       -(*)        exec xdg-open "$s" ;;
       -esac
 (DIR) diff --git a/bin/io-find b/bin/iomenu-find
 (DIR) diff --git a/bin/io-fstab b/bin/iomenu-fstab
 (DIR) diff --git a/bin/io-man b/bin/iomenu-man
 (DIR) diff --git a/bin/io-net b/bin/iomenu-net
 (DIR) diff --git a/bin/io-passwd b/bin/iomenu-passwd
 (DIR) diff --git a/bin/io-ps b/bin/iomenu-ps
 (DIR) diff --git a/config.mk b/config.mk
       @@ -1,4 +0,0 @@
       -PREFIX = /usr/local
       -MANPREFIX = ${PREFIX}/man
       -CFLAGS = -std=c89 -pedantic -Wall -Wextra -g -D_POSIX_C_SOURCE=200809L
       -LFLAGS =
 (DIR) diff --git a/iomenu.c b/iomenu.c
       @@ -1,5 +1,3 @@
       -#include <sys/ioctl.h>
       -
        #include <ctype.h>
        #include <errno.h>
        #include <fcntl.h>
       @@ -9,25 +7,29 @@
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       +#include <sys/ioctl.h>
        #include <termios.h>
        #include <unistd.h>
        
       -#include "utf8.h"
        #include "compat.h"
       -#include "util.h"
        #include "log.h"
       +#include "mem.h"
       +#include "term.h"
       +#include "utf8.h"
        
       -#ifndef SIGWINCH
       -#define SIGWINCH        28
       -#endif
       +struct {
       +        FILE *tty;
       +        char input[LINE_MAX];
       +        size_t cur;
       +
       +        char **lines_buf;
       +        size_t lines_len;
       +
       +        char **match_buf;
       +        size_t match_len;
       +} ctx;
        
       -static struct termios termios;
       -struct winsize ws;
       -static int linec = 0, matchc = 0, cur = 0;
       -static char **linev = NULL, **matchv = NULL;
       -static char input[LINE_MAX];
       -static char *flag['z'];
       -char *argv0;
       +int opt_comment;
        
        /*
         * Keep the line if it match every token (in no particular order,
       @@ -36,7 +38,7 @@ char *argv0;
        static int
        match_line(char *line, char **tokv)
        {
       -        if (flag['#'] && line[0] == '#')
       +        if (opt_comment && line[0] == '#')
                        return 2;
                for (; *tokv != NULL; tokv++)
                        if (strcasestr(line, *tokv) == NULL)
       @@ -49,277 +51,203 @@ match_line(char *line, char **tokv)
         * error message.
         */
        static void
       -iomenu_die(const char *s)
       +goodbye(const char *s)
        {
                int e = errno;
        
       -        if (tcsetattr(STDERR_FILENO, TCSANOW, &termios) == -1)
       -                warn("tcsetattr while dying");
       +        term_raw_off(2);
                errno = e;
                die("%s", s);
        }
        
       -/*
       - * Read one key from stdin and die if it failed to prevent to read
       - * in an endless loop.  This caused the load average to go over 10
       - * at work.  :S
       - */
       -int
       -getkey(void)
       -{
       -        int c;
       -
       -        if ((c = fgetc(stdin)) == EOF)
       -                iomenu_die("getting a key");
       -        return c;
       -}
       -
       -/*
       - * Split a buffer into an array of lines, without allocating memory for every
       - * line, but using the input buffer and replacing '\n' by '\0'.
       - */
       -static void
       -split_lines(char *buf)
       -{
       -        char        *b, **lv, **mv;
       -
       -        linec = 1;
       -        for (b = buf; (b = strchr(b, '\n')) != NULL && b[1] != '\0'; b++)
       -                linec++;
       -        if ((lv = linev = calloc(linec + 1, sizeof(char **))) == NULL)
       -                iomenu_die("calloc");
       -        if ((mv = matchv = calloc(linec + 1, sizeof(char **))) == NULL)
       -                iomenu_die("calloc");
       -        *mv = *lv = b = buf;
       -        while ((b = strchr(b, '\n')) != NULL) {
       -                *b = '\0';
       -                *++mv = *++lv = ++b;
       -        }
       -}
       -
       -/*
       - * Read stdin in a single malloc-ed buffer, realloc-ed to twice its size every
       - * time the previous buffer is filled.
       - */
       -static void
       -read_stdin(void)
       -{
       -        size_t size, len, off;
       -        char *buf;
       -
       -        size = BUFSIZ;
       -        off = 0;
       -        if ((buf = malloc(size)) == NULL)
       -                iomenu_die("malloc");
       -        while ((len = read(STDIN_FILENO, buf + off, size - off)) > 0) {
       -                off += len;
       -                if (off == size) {
       -                        size *= 2;
       -                        if ((buf = realloc(buf, size + 1)) == NULL)
       -                                iomenu_die("realloc");
       -                }
       -        }
       -        buf[off] = '\0';
       -        split_lines(buf);
       -}
       -
        static void
       -move(int sign)
       +do_move(int sign)
        {
       -        int i;
       -
       -        for (i = cur + sign; 0 <= i && i < matchc; i += sign) {
       -                if (flag['#'] == 0 || matchv[i][0] != '#') {
       -                        cur = i;
       +        /* integer overflow will do what we need */
       +        for (size_t i = ctx.cur + sign; i < ctx.match_len; i += sign) {
       +                if (opt_comment == 0 || ctx.match_buf[i][0] != '#') {
       +                        ctx.cur = i;
                                break;
                        }
                }
        }
        
       -static void
       -tokenize(char **tokv, char *str)
       -{
       -        while ((*tokv = strsep(&str, " \t")) != NULL) {
       -                if (**tokv != '\0')
       -                        tokv++;
       -        }
       -        *tokv = NULL;
       -}
       -
        /*
         * First split input into token, then match every token independently against
       - * every line.  The matching lines fills matchv.  Matches are searched inside
       + * every line.  The matching lines fills matches.  Matches are searched inside
         * of `searchv' of size `searchc'
         */
        static void
       -filter(int searchc, char **searchv)
       +do_filter(char **search_buf, size_t search_len)
        {
       -        int n;
       -        char *tokv[sizeof(input) * sizeof(char *) + sizeof(NULL)];
       -        char buf[sizeof(input)];
       -
       -        strncpy(buf, input, sizeof(input));
       -        buf[sizeof(input) - 1] = '\0';
       -        tokenize(tokv, buf);
       -
       -        cur = matchc = 0;
       -        for (n = 0; n < searchc; n++)
       -                if (match_line(searchv[n], tokv))
       -                        matchv[matchc++] = searchv[n];
       -        if (flag['#'] && matchv[cur][0] == '#')
       -                move(+1);
       +        char **t, *tokv[(sizeof ctx.input + 1) * sizeof(char *)];
       +        char *b, buf[sizeof ctx.input];
       +
       +        strlcpy(buf, ctx.input, sizeof buf);
       +
       +        for (b = buf, t = tokv; (*t = strsep(&b, " \t")) != NULL; t++)
       +                continue;
       +        *t = NULL;
       +
       +        ctx.cur = ctx.match_len = 0;
       +        for (size_t n = 0; n < search_len; n++)
       +                if (match_line(search_buf[n], tokv))
       +                        ctx.match_buf[ctx.match_len++] = search_buf[n];
       +        if (opt_comment && ctx.match_buf[ctx.cur][0] == '#')
       +                do_move(+1);
        }
        
        static void
       -move_page(signed int sign)
       +do_move_page(signed int sign)
        {
       -        int i, rows;
       +        int rows = term.winsize.ws_row - 1;
       +        size_t i = ctx.cur - ctx.cur % rows + rows * sign;
        
       -        rows = ws.ws_row - 1;
       -        i = cur - cur % rows + rows * sign;
       -        if (!(0 <= i && i < matchc))
       +        if (i >= ctx.match_len)
                        return;
       -        cur = i - 1;
       -        move(+1);
       +        ctx.cur = i - 1;
       +
       +        do_move(+1);
        }
        
        static void
       -move_header(signed int sign)
       +do_move_header(signed int sign)
        {
       -        move(sign);
       -        if (flag['#'] == 0)
       +        do_move(sign);
       +
       +        if (opt_comment == 0)
                        return;
       -        for (cur += sign; 0 <= cur; cur += sign) {
       -                if (cur >= matchc) {
       -                        cur--;
       +        for (ctx.cur += sign;; ctx.cur += sign) {
       +                char *cur = ctx.match_buf[ctx.cur];
       +
       +                if (ctx.cur >= ctx.match_len) {
       +                        ctx.cur--;
                                break;
                        }
       -                if (matchv[cur][0] == '#')
       +                if (cur[0] == '#')
                                break;
                }
       -        move(+1);
       +
       +        do_move(+1);
        }
        
        static void
       -remove_word()
       +do_remove_word(void)
        {
                int len, i;
        
       -        len = strlen(input) - 1;
       -        for (i = len; i >= 0 && isspace(input[i]); i--)
       -                input[i] = '\0';
       -        len = strlen(input) - 1;
       -        for (i = len; i >= 0 && !isspace(input[i]); i--)
       -                input[i] = '\0';
       -        filter(linec, linev);
       +        len = strlen(ctx.input) - 1;
       +        for (i = len; i >= 0 && isspace(ctx.input[i]); i--)
       +                ctx.input[i] = '\0';
       +        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);
        }
        
        static void
       -add_char(char c)
       +do_add_char(char c)
        {
                int len;
        
       -        len = strlen(input);
       +        len = strlen(ctx.input);
       +        if (len + 1 == sizeof ctx.input)
       +                return;
                if (isprint(c)) {
       -                input[len]     = c;
       -                input[len + 1] = '\0';
       +                ctx.input[len] = c;
       +                ctx.input[len + 1] = '\0';
                }
       -        filter(matchc, matchv);
       +        do_filter(ctx.match_buf, ctx.match_len);
        }
        
        static void
       -print_selection(void)
       +do_print_selection(void)
        {
       -        char **match;
       +        if (opt_comment) {
       +                char **match = ctx.match_buf + ctx.cur;
        
       -        if (flag['#']) {
       -                match = matchv + cur;
       -                while (--match >= matchv) {
       +                while (--match >= ctx.match_buf) {
                                if ((*match)[0] == '#') {
       -                                fputs(*match + 1, stdout);
       +                                fprintf(stdout, "%s", *match + 1);
                                        break;
                                }
                        }
       -                putchar('\t');
       +                fprintf(stdout, "%c", '\t');
                }
       -        if (matchc == 0 || (flag['#'] && matchv[cur][0] == '#'))
       -                puts(input);
       +        term_raw_off(2);
       +        if (ctx.match_len == 0
       +          || (opt_comment && ctx.match_buf[ctx.cur][0] == '#'))
       +                fprintf(stdout, "%s\n", ctx.input);
                else
       -                puts(matchv[cur]);
       +                fprintf(stdout, "%s\n", ctx.match_buf[ctx.cur]);
       +        term_raw_on(2);
        }
        
        /*
       - * Big case table, that calls itself back for with ALT (aka Esc), CSI
       + * Big case table, that calls itself back for with TERM_KEY_ALT (aka Esc), TERM_KEY_CSI
         * (aka Esc + [).  These last two have values above the range of ASCII.
         */
       -int
       -key(void)
       +static int
       +key_action(void)
        {
       -        int k;
       -        k = getkey();
       -top:
       -        switch (k) {
       -        case CTL('C'):
       +        int key;
       +
       +        key = term_get_key(stderr);
       +        switch (key) {
       +        case TERM_KEY_CTRL('Z'):
       +                term_raw_off(2);
       +                kill(getpid(), SIGSTOP);
       +                term_raw_on(2);
       +                break;
       +        case TERM_KEY_CTRL('C'):
       +        case TERM_KEY_CTRL('D'):
                        return -1;
       -        case CTL('U'):
       -                input[0] = '\0';
       -                filter(linec, linev);
       +        case TERM_KEY_CTRL('U'):
       +                ctx.input[0] = '\0';
       +                do_filter(ctx.lines_buf, ctx.lines_len);
                        break;
       -        case CTL('W'):
       -                remove_word();
       +        case TERM_KEY_CTRL('W'):
       +                do_remove_word();
                        break;
       -        case 127:
       -        case CTL('H'): /* backspace */
       -                input[strlen(input) - 1] = '\0';
       -                filter(linec, linev);
       +        case TERM_KEY_DELETE:
       +        case TERM_KEY_BACKSPACE:
       +                ctx.input[strlen(ctx.input) - 1] = '\0';
       +                do_filter(ctx.lines_buf, ctx.lines_len);
                        break;
       -        case CSI('A'): /* up */
       -        case CTL('P'):
       -                move(-1);
       +        case TERM_KEY_ARROW_UP:
       +        case TERM_KEY_CTRL('P'):
       +                do_move(-1);
                        break;
       -        case ALT('p'):
       -                move_header(-1);
       +        case TERM_KEY_ALT('p'):
       +                do_move_header(-1);
                        break;
       -        case CSI('B'): /* down */
       -        case CTL('N'):
       -                move(+1);
       +        case TERM_KEY_ARROW_DOWN:
       +        case TERM_KEY_CTRL('N'):
       +                do_move(+1);
                        break;
       -        case ALT('n'):
       -                move_header(+1);
       +        case TERM_KEY_ALT('n'):
       +                do_move_header(+1);
                        break;
       -        case CSI('5'): /* page up */
       -                if (getkey() != '~')
       -                        break;
       -                /* FALLTHROUGH */
       -        case ALT('v'):
       -                move_page(-1);
       +        case TERM_KEY_PAGE_UP:
       +        case TERM_KEY_ALT('v'):
       +                do_move_page(-1);
                        break;
       -        case CSI('6'): /* page down */
       -                if (getkey() != '~')
       -                        break;
       -                /* FALLTHROUGH */
       -        case CTL('V'):
       -                move_page(+1);
       +        case TERM_KEY_PAGE_DOWN:
       +        case TERM_KEY_CTRL('V'):
       +                do_move_page(+1);
                        break;
       -        case CTL('I'): /* tab */
       -                if (linec > 0) {
       -                        strncpy(input, matchv[cur], sizeof(input));
       -                        input[sizeof(input) - 1] = '\0';
       -                }
       -                filter(matchc, matchv);
       +        case TERM_KEY_TAB:
       +                if (ctx.match_len == 0)
       +                        break;
       +                strlcpy(ctx.input, ctx.match_buf[ctx.cur], sizeof(ctx.input));
       +                do_filter(ctx.match_buf, ctx.match_len);
                        break;
       -        case CTL('J'):/* enter */
       -        case CTL('M'):
       -                print_selection();
       +        case TERM_KEY_ENTER:
       +        case TERM_KEY_CTRL('M'):
       +                do_print_selection();
                        return 0;
       -        case ALT('['):
       -                k = CSI(getkey());
       -                goto top;
       -        case ESC:
       -                k = ALT(getkey());
       -                goto top;
                default:
       -                add_char((char) k);
       +                do_add_char(key);
                }
        
                return 1;
       @@ -328,80 +256,93 @@ top:
        static void
        print_line(char *line, int highlight)
        {
       -        if (flag['#'] && line[0] == '#')
       +        if (opt_comment && line[0] == '#') {
                        fprintf(stderr, "\n\x1b[1m\r%.*s\x1b[m",
       -                    utf8_col(line + 1, ws.ws_col, 0), line + 1);
       -        else if (highlight)
       +                  term_at_width(line + 1, term.winsize.ws_col, 0), line + 1);
       +        } else if (highlight) {
                        fprintf(stderr, "\n\x1b[47;30m\x1b[K\r%.*s\x1b[m",
       -                    utf8_col(line, ws.ws_col, 0), line);
       -        else
       +                  term_at_width(line, term.winsize.ws_col, 0), line);
       +        } else {
                        fprintf(stderr, "\n%.*s",
       -                    utf8_col(line, ws.ws_col, 0), line);
       +                  term_at_width(line, term.winsize.ws_col, 0), line);
       +        }
        }
        
        static void
       -print_screen(void)
       +do_print_screen(void)
        {
                char **m;
       -        int p, i, c, cols, rows;
       +        int p, c, cols, rows;
       +        size_t i;
        
       -        cols = ws.ws_col;
       -        rows = ws.ws_row - 1; /* -1 to keep one line for user input */
       +        cols = term.winsize.ws_col;
       +        rows = term.winsize.ws_row - 1; /* -1 to keep one line for user input */
                p = c = 0;
       -        i = cur - cur % rows;
       -        m = matchv + i;
       -        fputs("\x1b[2J", stderr);
       -        while (p < rows && i < matchc) {
       -                print_line(*m, i == cur);
       +        i = ctx.cur - ctx.cur % rows;
       +        m = ctx.match_buf + i;
       +        fprintf(stderr, "\x1b[2J");
       +        while (p < rows && i < ctx.match_len) {
       +                print_line(*m, i == ctx.cur);
                        p++, i++, m++;
                }
       -        fputs("\x1b[H", stderr);
       -        fprintf(stderr, "%.*s", utf8_col(input, cols, c), input);
       +        fprintf(stderr, "\x1b[H%.*s",
       +          term_at_width(ctx.input, cols, c), ctx.input);
                fflush(stderr);
        }
        
       -/*
       - * Set terminal to raw mode.
       - */
        static void
       -term_set(void)
       +sig_winch(int sig)
        {
       -        struct termios new;
       -
       -        fputs("\x1b[s\x1b[?1049h\x1b[H", stderr);
       -        if (tcgetattr(STDERR_FILENO, &termios) == -1 ||
       -            tcgetattr(STDERR_FILENO, &new) == -1)
       -                iomenu_die("setting terminal");
       -        new.c_lflag &= ~(ICANON | ECHO | IEXTEN | IGNBRK | ISIG);
       -        if (tcsetattr(STDERR_FILENO, TCSANOW, &new) == -1)
       -                iomenu_die("tcsetattr");
       +        if (ioctl(STDERR_FILENO, TIOCGWINSZ, &term.winsize) == -1)
       +                goodbye("ioctl");
       +        do_print_screen();
       +        signal(sig, sig_winch);
        }
        
       -/*
       - * Take terminal out of raw mode.
       - */
        static void
       -term_reset(void)
       +usage(char const *arg0)
        {
       -        fputs("\x1b[2J\x1b[u\033[?1049l", stderr);
       -        if (tcsetattr(STDERR_FILENO, TCSANOW, &termios))
       -                iomenu_die("resetting terminal");
       +        fprintf(stderr, "usage: %s [-#] <lines\n", arg0);
       +        exit(1);
        }
        
        static void
       -sigwinch(int sig)
       +read_stdin(char **buf, struct mem_pool *pool)
        {
       -        if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1)
       -                iomenu_die("ioctl");
       -        print_screen();
       -        signal(sig, sigwinch);
       +        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");
        }
        
       +/*
       + * Split a buffer into an array of lines, without allocating memory for every
       + * line, but using the input buffer and replacing '\n' by '\0'.
       + */
        static void
       -usage(char const *arg0)
       +split_lines(char *s, struct mem_pool *pool)
        {
       -        fprintf(stderr, "usage: %s [-#]\n", arg0);
       -        exit(1);
       +        ctx.lines_buf = mem_alloc(pool, 0);
       +        if (ctx.lines_buf == NULL)
       +                goodbye("initializing full lines buffer");
       +
       +        ctx.lines_len = 0;
       +        for (;;) {
       +                if (mem_append((void **)&ctx.lines_buf, &s, sizeof s) < 0)
       +                        goodbye("adding line to array");
       +                ctx.lines_len++;
       +
       +                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");
        }
        
        /*
       @@ -412,32 +353,49 @@ usage(char const *arg0)
        int
        main(int argc, char *argv[])
        {
       +        struct mem_pool pool = {0};
       +        char *buf;
       +
                arg0 = *argv;
       -        for (int c; (c = getopt(argc, argv, "#")) > 0; flag[c] = optarg)
       -                if (c == '?')
       +        for (int opt; (opt = getopt(argc, argv, "#v")) > 0;) {
       +                switch (opt) {
       +                case 'v':
       +                        fprintf(stdout, "%s\n", VERSION);
       +                        exit(0);
       +                case '#':
       +                        opt_comment = 1;
       +                        break;
       +                default:
                                usage(arg0);
       +                }
       +        }
                argc -= optind;
                argv += optind;
        
       -        input[0] = '\0';
       -
       -        read_stdin();
       +        read_stdin(&buf, &pool);
       +        split_lines(buf, &pool);
        
       -        filter(linec, linev);
       +        do_filter(ctx.lines_buf, ctx.lines_len);
        
                if (!isatty(2))
       -                iomenu_die("file descriptor 2 (stderr)");
       +                goodbye("file descriptor 2 (stderr)");
        
       -        term_set();
       -        sigwinch(SIGWINCH);
       +        freopen("/dev/tty", "w+", stderr);
       +        if (stderr == NULL)
       +                goodbye("re-opening standard error read/write");
       +
       +        term_raw_on(2);
       +        sig_winch(SIGWINCH);
        
        #ifdef __OpenBSD__
                pledge("stdio tty", NULL);
        #endif
        
       -        while (key() > 0)
       -                print_screen();
       -        term_reset();
       +        while (key_action() > 0)
       +                do_print_screen();
       +
       +        term_raw_off(2);
        
       +        mem_free(&pool);
                return 0;
        }
 (DIR) diff --git a/src/compat.h b/src/compat.h
       @@ -1,11 +1,18 @@
        #ifndef COMPAT_H
        #define COMPAT_H
        
       +#include <stddef.h>
       +#include <wchar.h>
       +
        #define wcwidth(c) mk_wcwidth_cjk(c)
       -#define wcswidth(s, n) mk_wcwidth_cjk(s, n)
        
        /** src/compat/?*.c **/
        char * strcasestr(const char *str1, const char *str2);
       +size_t strlcpy(char *buf, char const *str, size_t sz);
        char * strsep(char **str_p, char const *sep);
       +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
 (DIR) diff --git a/src/compat/strlcpy.c b/src/compat/strlcpy.c
       @@ -0,0 +1,15 @@
       +#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/log.c b/src/log.c
       @@ -1,34 +1,24 @@
        #include "log.h"
        
        #include <assert.h>
       -#include <string.h>
       -
       -/*
       - * log.c - log to standard error according to the log level
       - *
       - * Instead of logging to syslog, delegate logging to a separate
       - * tool, such as FreeBSD's daemon(8), POSIX's logger(1).
       - */
       -
        #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_print(int level, char const *flag, char const *fmt, ...)
       +log_vprintf(int level, char const *flag, char const *fmt, va_list va)
        {
       -        va_list va;
                char *env;
                int old_errno = errno;
        
       -        va_start(va, fmt);
       -
                if (log_level < 0) {
                        env = getenv("LOG");
                        log_level = (env == NULL) ? 0 : atoi(env);
       @@ -50,5 +40,33 @@ log_print(int level, char const *flag, char const *fmt, ...)
        
                fprintf(stderr, "\n");
                fflush(stderr);
       -        va_end(va);
       +}
       +
       +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
       @@ -5,11 +5,10 @@
        
        /** src/log.c **/
        char *arg0;
       -void log_print(int level, char const *flag, char const *fmt, ...);
       -
       -#define die(...) (log_print(1, "error", __VA_ARGS__), exit(1))
       -#define warn(...) log_print(2, "warn", __VA_ARGS__)
       -#define info(...) log_print(3, "info", __VA_ARGS__)
       -#define debug(...) log_print(4, "debug", __VA_ARGS__)
       +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,15 +1,16 @@
        #include "mem.h"
        
        #include <assert.h>
       +#include <errno.h>
       +#include <stdint.h>
        #include <stdlib.h>
        #include <string.h>
       -#include <stdint.h>
       -#include <errno.h>
       +#include <unistd.h>
        
        static struct mem_block *
       -mem_block(void *v)
       +mem_block(void **memp)
        {
       -        struct mem_block *block = (void *)((char *)v - sizeof *block);
       +        struct mem_block *block = (void *)((char *)memp - sizeof *block);
        
                assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
                return block;
       @@ -37,9 +38,9 @@ mem_alloc(struct mem_pool *pool, size_t len)
        }
        
        int
       -mem_resize(void **pp, size_t len)
       +mem_resize(void **memp, size_t len)
        {
       -        struct mem_block *block = mem_block(*pp);
       +        struct mem_block *block = mem_block(*memp);
                int is_first = (block == block->pool->head);
                int is_same;
                void *v;
       @@ -61,53 +62,80 @@ mem_resize(void **pp, size_t len)
                        block->next->prev = v;
                if (is_first)
                        block->pool->head = v;
       -        *pp = block->buf;
       +        *memp = block->buf;
        
                assert(memcmp(block->magic, MEM_BLOCK_MAGIC, 8) == 0);
                return 0;
        }
        
        int
       -mem_grow(void **pp, size_t len)
       +mem_grow(void **memp, size_t len)
        {
       -        assert(SIZE_MAX - len >= mem_block(*pp)->len);
       +        assert(SIZE_MAX - len >= mem_block(*memp)->len);
        
       -        return mem_resize(pp, mem_length(*pp) + len);
       +        return mem_resize(memp, mem_length(*memp) + len);
        }
        
        int
       -mem_shrink(void **pp, size_t len)
       +mem_shrink(void **memp, size_t len)
        {
       -        assert(mem_block(*pp)->len >= len);
       +        assert(mem_block(*memp)->len >= len);
        
       -        return mem_resize(pp, mem_length(*pp) - len);
       +        return mem_resize(memp, mem_length(*memp) - len);
        }
        
        size_t
       -mem_length(void *v)
       +mem_length(void *mem)
        {
       -        return mem_block(v)->len;
       +        return mem_block(mem)->len;
        }
        
        int
       -mem_append(void **pp, char const *buf, size_t len)
       +mem_append(void **memp, void const *buf, size_t len)
        {
       -        size_t old_len = mem_length(*pp);
       +        size_t old_len = mem_length(*memp);
                struct mem_block *block;
        
       -        if (mem_grow(pp, len) < 0)
       +        if (mem_grow(memp, len) < 0)
                        return -1;
       -        block = mem_block(*pp);
       +        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 *v)
       +mem_delete(void *mem)
        {
       -        struct mem_block *block = mem_block(v);;
       +        struct mem_block *block = mem_block(mem);;
        
                if (block == block->pool->head)
                        block->pool->head = block->next;
 (DIR) diff --git a/src/mem.h b/src/mem.h
       @@ -47,12 +47,13 @@ struct mem_block {
        
        /** src/mem.c **/
        void * mem_alloc(struct mem_pool *pool, size_t len);
       -int mem_resize(void **pp, size_t len);
       -int mem_grow(void **pp, size_t len);
       -int mem_shrink(void **pp, size_t len);
       -size_t mem_length(void *v);
       -int mem_append(void **pp, char const *buf, size_t len);
       -void mem_delete(void *v);
       +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/str.c b/src/str.c
       @@ -1,119 +0,0 @@
       -#include "str.h"
       -
       -#include <assert.h>
       -#include <errno.h>
       -#include <stdarg.h>
       -#include <stdint.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -
       -#include "mem.h"
       -
       -/*
       - * It is heavy on assert as an inexpensive way to detect memory corruption from
       - * code around: underflows are checked through the magic header, overflow are
       - * checked through the '\0' byte.
       - */
       -
       -size_t
       -str_length(struct str *str)
       -{
       -        return mem_length(str->mem) - sizeof "";
       -}
       -
       -int
       -str_init(struct str *str, struct mem_pool *pool)
       -{
       -        str->mem = mem_alloc(pool, 1);
       -        if (str->mem == NULL)
       -                return -1;
       -
       -        assert(str->mem[0] == '\0');
       -        assert(str_length(str) == 0);
       -        assert(mem_length(str->mem) == 1);
       -        return 0;
       -}
       -
       -void
       -str_truncate(struct str *str, size_t len)
       -{
       -        assert(str->mem[str_length(str)] == '\0');
       -
       -        if (mem_length(str->mem) > len)
       -                str->mem[len] = '\0';
       -        mem_resize((void **)&str->mem, len + 1);
       -
       -        assert(str->mem[str_length(str)] == '\0');
       -}
       -
       -int
       -str_append_mem(struct str *str, char const *buf, size_t len)
       -{
       -        void **pp = (void **)&str->mem;
       -        size_t old_sz;
       -
       -        assert((old_sz = mem_length(str->mem)) > 0);
       -        assert(memchr(buf, '\0', len) == NULL);
       -        assert(str->mem[str_length(str)] == '\0');
       -
       -        if (mem_shrink(pp, 1)) /* strip '\0' */
       -                return -1;
       -        if (mem_append(pp, buf, len) < 0)
       -                return -1;
       -        if (mem_append(pp, "", 1) < 0)
       -                return -1;
       -
       -        assert(memcmp(str->mem + old_sz-1, buf, len) == 0);
       -        assert(str->mem[old_sz-1 + len] == '\0');
       -        assert(str->mem[str_length(str)] == '\0');
       -        return 0;
       -}
       -
       -int
       -str_append_string(struct str *str, char const *s)
       -{
       -        assert(str->mem[str_length(str)] == '\0');
       -
       -        return str_append_mem(str, s, strlen(s));
       -}
       -
       -int
       -str_append_char(struct str *str, char c)
       -{
       -        assert(str->mem[str_length(str)] == '\0');
       -
       -        str_c(str)[str_length(str)] = c;
       -        if (mem_append((void **)str->mem, "", 1) < 0)
       -                return -1;
       -
       -        assert(str->mem[str_length(str)] == '\0');
       -        return 0;
       -}
       -
       -int
       -str_append_fmt(struct str *str, char const *fmt, ...)
       -{
       -        va_list va;
       -        int n;
       -
       -        assert(str->mem[str_length(str)] == '\0');
       -
       -        va_start(va, fmt);
       -        n = vsnprintf(NULL, 0, fmt, va);
       -        assert(n > 0);
       -        if (mem_grow((void **)&str->mem, n + 1) < 0)
       -                return -1;
       -        vsprintf(str->mem, fmt, va);
       -        va_end(va);
       -
       -        assert(str->mem[str_length(str)] == '\0');
       -        return 0;
       -}
       -
       -char *
       -str_c(struct str *str)
       -{
       -        assert(str->mem[str_length(str)] == '\0');
       -        return str->mem;
       -}
 (DIR) diff --git a/src/str.h b/src/str.h
       @@ -1,26 +0,0 @@
       -#ifndef STR_H
       -#define STR_H
       -
       -#include <stddef.h>
       -
       -#include "mem.h"
       -
       -/*
       - * Length kept by struct mem_block.
       - */
       -
       -struct str {
       -        char *mem;
       -};
       -
       -/** src/str.c **/
       -size_t str_length(struct str *str);
       -int str_init(struct str *str, struct mem_pool *pool);
       -void str_truncate(struct str *str, size_t len);
       -int str_append_mem(struct str *str, char const *buf, size_t len);
       -int str_append_string(struct str *str, char const *s);
       -int str_append_char(struct str *str, char c);
       -int str_append_fmt(struct str *str, char const *fmt, ...);
       -char * str_c(struct str *str);
       -
       -#endif
 (DIR) diff --git a/src/term.c b/src/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 "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
       @@ -0,0 +1,39 @@
       +#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/src/utf8.c b/src/utf8.c
       @@ -1,214 +1,85 @@
       -/*
       - * ASCII all have a leading '0' byte:
       - *
       - *   0xxxxxxx
       - *
       - * UTF-8 have one leading '1' and as many following '1' as there are
       - * continuation bytes (with leading '1' and '0').
       - *
       - *   0xxxxxxx
       - *   110xxxxx 10xxxxxx
       - *   1110xxxx 10xxxxxx 10xxxxxx
       - *   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
       - *   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
       - *   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
       - *
       - * There is up to 3 continuation bytes -- up to 4 bytes per runes.
       - */
       -
       -#include <ctype.h>
       -#include <stddef.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <stdio.h>
       -
        #include "utf8.h"
        
       -static int
       -utflen(char const *str)
       -{
       -        int i, len;
       -        unsigned char const *s;
       -
       -        s = (unsigned char const *)str;
       -        len =        (*s < 0x80) ? 1 : /* 0xxxxxxx < *s < 10000000 */
       -                (*s < 0xc0) ? 0 : /* 10xxxxxx < *s < 11000000 */
       -                (*s < 0xe0) ? 2 : /* 110xxxxx < *s < 11100000 */
       -                (*s < 0xf0) ? 3 : /* 1110xxxx < *s < 11110000 */
       -                (*s < 0xf8) ? 4 : /* 11110xxx < *s < 11111000 */
       -                (*s < 0xfc) ? 5 : /* 111110xx < *s < 11111100 */
       -                (*s < 0xfe) ? 6 : /* 1111110x < *s < 11111110 */
       -                (*s < 0xff) ? 7 : /* 11111110 < *s < 11111111 */
       -                0;
       -
       -        /* check continuation bytes and '\0' */
       -        for (s++, i = 1; i < len; i++, s++) {
       -                if ((*s & 0xc0) != 0x80) /* 10xxxxxx & 11000000 */
       -                        return 0;
       -        }
       -
       -        return len;
       -}
       -
       -static long
       -torune(char const **str, size_t len)
       -{
       -        long rune;
       -        int n;
       -        char mask[] = { 0x00, 0x7f, 0x1f, 0x0f, 0x07, 0x03, 0x01 };
       -
       -        if (len == 0 || len > 6)
       -                return 0;
       -
       -        /* first byte */
       -        rune = *(*str)++ & mask[len];
       -
       -        /* continuation bytes */
       -        for (n = len - 1; n > 0; n--, (*str)++)
       -                rune = (rune << 6) | (**str & 0x3f);        /* 10xxxxxx */
       -
       -        return rune;
       -}
       -
       -/*
       - * Return the number of bytes required to encode <rune> into UTF-8, or
       - * 0 if rune is too long.
       - */
       -int
       -utf8_runelen(long rune)
       -{
       -        return        (rune <= 0x0000007f) ? 1 :
       -                (rune <= 0x000007ff) ? 2 :
       -                (rune <= 0x0000ffff) ? 3 :
       -                (rune <= 0x001fffff) ? 4 :
       -                (rune <= 0x03ffffff) ? 5 :
       -                (rune <= 0x7fffffff) ? 6 :
       -                0;
       -}
       +#include <stddef.h>
       +#include <stdint.h>
        
        /*
       - * Return the number of bytes in rune for the next char in <s>, or 0 if
       - * is misencoded or if it is '\0'.
       + * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
       + *
       + * Permission is hereby granted, free of charge, to any person obtaining a copy
       + * of this software and associated documentation files (the "Software"), to
       + * deal in the Software without restriction, including without limitation the
       + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
       + * sell copies of the Software, and to permit persons to whom the Software is
       + * furnished to do so, subject to the following conditions:
       + *
       + * The above copyright notice and this permission notice shall be included in
       + * all copies or substantial portions of the Software.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
       + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
       + * IN THE SOFTWARE.
         */
       -int
       -utf8_utflen(char const *str)
       -{
       -        long rune;
       -        int len = utflen(str);
       -
       -        rune = torune(&str, len);
       -        if (len != utf8_runelen(rune))
       -                return 0;
       -
       -        return len;
       -}
        
       -/*
       - * Return a rune corresponding to the firsts bytes of <str> or -1 if
       - * the rune is invalid, and set <str> to the beginning of the next rune.
       - */
       -long
       -utf8_torune(char const **str)
       +size_t
       +utf8_encode(char *dest, uint32_t u)
        {
       -        long rune;
       -        int len;
       +        size_t v, n, n2;
        
       -        len = utflen(*str);
       -        rune = torune(str, len);
       -        if (len != utf8_runelen(rune))
       -                return -1;
       -
       -        return rune;
       -}
       -
       -/*
       - * Encode the rune <rune> in UTF-8 in <str>, null-terminated, then return the
       - * number of bytes written, 0 if <rune> is invalid.
       - *
       - * Thanks to Connor Lane Smith for the idea of combining switches and
       - * binary masks.
       - */
       -int
       -utf8_tostr(char *str, long rune)
       -{
       -        switch (utf8_runelen(rune)) {
       -        case 1:
       -                str[0] = rune;                                /* 0xxxxxxx */
       -                str[1] = '\0';
       +        if (u <= 0x7f) {
       +                if (dest != NULL)
       +                        *dest = u;
                        return 1;
       -        case 2:
       -                str[0] = 0xc0 | (0x1f & (rune >> 6));        /* 110xxxxx */
       -                str[1] = 0x80 | (0x3f & (rune));        /* 10xxxxxx */
       -                str[2] = '\0';
       -                return 2;
       -        case 3:
       -                str[0] = 0xe0 | (0x0f & (rune >> 12));        /* 1110xxxx */
       -                str[1] = 0x80 | (0x3f & (rune >> 6));        /* 10xxxxxx */
       -                str[2] = 0x80 | (0x3f & (rune));        /* 10xxxxxx */
       -                str[3] = '\0';
       -                return 3;
       -        case 4:
       -                str[0] = 0xf0 | (0x07 & (rune >> 18));        /* 11110xxx */
       -                str[1] = 0x80 | (0x3f & (rune >> 12));        /* 10xxxxxx */
       -                str[2] = 0x80 | (0x3f & (rune >> 6));        /* 10xxxxxx */
       -                str[3] = 0x80 | (0x3f & (rune));        /* 10xxxxxx */
       -                str[4] = '\0';
       -                return 4;
       -        case 5:
       -                str[0] = 0xf8 | (0x03 & (rune >> 24));        /* 111110xx */
       -                str[1] = 0x80 | (0x3f & (rune >> 18));        /* 10xxxxxx */
       -                str[2] = 0x80 | (0x3f & (rune >> 12));        /* 10xxxxxx */
       -                str[3] = 0x80 | (0x3f & (rune >> 6));        /* 10xxxxxx */
       -                str[4] = 0x80 | (0x3f & (rune));        /* 10xxxxxx */
       -                str[5] = '\0';
       -                return 5;
       -        case 6:
       -                str[0] = 0xfc | (0x01 & (rune >> 30));        /* 1111110x */
       -                str[1] = 0x80 | (0x3f & (rune >> 24));        /* 10xxxxxx */
       -                str[2] = 0x80 | (0x3f & (rune >> 18));        /* 10xxxxxx */
       -                str[3] = 0x80 | (0x3f & (rune >> 12));        /* 10xxxxxx */
       -                str[4] = 0x80 | (0x3f & (rune >> 6));        /* 10xxxxxx */
       -                str[5] = 0x80 | (0x3f & (rune));        /* 10xxxxxx */
       -                str[6] = '\0';
       -                return 6;
       -        default:
       -                str[0] = '\0';
       -                return 0;
                }
       -}
        
       -/*
       - * Return 1 if the rune is a printable character, and 0 otherwise.
       - */
       -int
       -utf8_isprint(long rune)
       -{
       -        return (0x1f < rune && rune != 0x7f && rune < 0x80) || 0x9f < rune;
       -}
       +        for (v = 0x3f, n = 0; v >= u; ++n)
       +                v = (v << 5) | 0x1f;
       +        if (v >= 0x7fffffff)
       +                return 0; /* cannot be encoded */
        
       -/*
       - * Return a pointer to the next rune in <str> or next byte if the rune
       - * is invalid.
       - */
       -char const *
       -utf8_nextrune(char const *str)
       -{
       -        int len;
       +        if (dest == NULL)
       +                return 1 + n;
        
       -        len = utf8_utflen(str);
       -        return (len == 0) ? str + 1 : str + len;
       +        *dest++ = (0xff << (7 - n)) | (u >> n * 6);
       +        for (n2 = n - 1; n2 ; --n2) {
       +                *dest++ = 0x80 | (u & 0x3f);
       +                u >>= 6;
       +        }
       +        return 1 + n;
        }
        
       -/*
       - * Return a pointer to the prev rune in <str> or prev byte if the rune
       - * is invalid.
       - */
       -char const *
       -utf8_prevrune(char const *str)
       +/* Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> *
       + * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */
       +
       +static const uint8_t utf8d[] = {
       +        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 00..1f */
       +        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 20..3f */
       +        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40..5f */
       +        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 60..7f */
       +        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, /* 80..9f */
       +        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* a0..bf */
       +        8,8,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, /* c0..df */
       +        0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, /* e0..ef */
       +        0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, /* f0..ff */
       +        0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, /* s0..s0 */
       +        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, /* s1..s2 */
       +        1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, /* s3..s4 */
       +        1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, /* s5..s6 */
       +        1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* s7..s8 */
       +};
       +
       +uint32_t
       +utf8_decode(uint32_t *state, uint32_t *codep, uint32_t byte)
        {
       -        char const *s;
       +        uint32_t type = utf8d[byte];
        
       -        for (s = str; (*s & 0x80) != 0; s--)
       -                ;
       -        return (utf8_utflen(s) != 0) ? s : str - 1;
       +        *codep = (*state != UTF8_ACCEPT)
       +                ? (byte & 0x3fu) | (*codep << 6)
       +                : (0xff >> type) & (byte);
       +        *state = utf8d[256 + *state*16 + type];
       +        return *state;
        }
 (DIR) diff --git a/src/utf8.h b/src/utf8.h
       @@ -1,13 +1,16 @@
        #ifndef UTF8_H
        #define UTF8_H
        
       +#include <stddef.h>
       +#include <stdint.h>
       +
       +enum {
       +        UTF8_ACCEPT,
       +        UTF8_REJECT,
       +};
       +
        /** src/utf8.c **/
       -int utf8_runelen(long rune);
       -int utf8_utflen(char const *str);
       -long utf8_torune(char const **str);
       -int utf8_tostr(char *str, long rune);
       -int utf8_isprint(long rune);
       -char const * utf8_nextrune(char const *str);
       -char const * utf8_prevrune(char const *str);
       +size_t utf8_encode(char *dest, uint32_t u);
       +uint32_t utf8_decode(uint32_t *state, uint32_t *codep, uint32_t byte);
        
        #endif
 (DIR) diff --git a/src/util.h b/src/util.h
       @@ -1,12 +0,0 @@
       -#ifndef UTIL_H
       -#define UTIL_H
       -
       -#define ESC                0x1b                        /* Esc key */
       -#define CTL(c)                ((c) & ~0x40)                /* Ctr + (c) key */
       -#define ALT(c)                ((c) + 0x80)                /* Alt + (c) key */
       -#define CSI(c)                ((c) + 0x80 + 0x80)        /* Escape + '[' + (c) code */
       -#define MIN(x, y)        (((x) < (y)) ? (x) : (y))
       -#define MAX(x, y)        (((x) > (y)) ? (x) : (y))
       -#define LEN(x)                (sizeof(x) / sizeof(*(x)))
       -
       -#endif
 (DIR) diff --git a/t/test.c b/t/test.c
       @@ -1,22 +0,0 @@
       -#include <stdio.h>
       -#include <wchar.h>
       -#include "utf8.h"
       -
       -int
       -main(void)
       -{
       -        int c, col, o, off;
       -        char s[] = "\t\t浪漫的夢想";
       -
       -        for (off = 0; off < 15; off++) {
       -                for (col = off + 1; col < 30; col++) {
       -                        for (c = 0; c < col; c++)
       -                                putchar(c % 8 == 0 ? '>' : '_');
       -                        printf(" %d\n", col);
       -                        for (o = 0; o < off; o++)
       -                                putchar('.');
       -                        printf("%.*s\n\n", utf8_col(s, col, off), s);
       -                }
       -        }
       -        return 0;
       -}