split into multiple files - 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 2743c784dbce7910cbec895c42a723f06fa7b620
 (DIR) parent e24cea688e69bd009ef52ea40891a0a386b881ea
 (HTM) Author: Josuah Demangeon <mail@josuah.net>
       Date:   Sun, 29 Oct 2017 22:05:58 +0100
       
       split into multiple files
       
       Diffstat:
         M Makefile                            |       4 +++-
         A buffer.c                            |      93 +++++++++++++++++++++++++++++++
         A buffer.h                            |       3 +++
         A control.c                           |     169 +++++++++++++++++++++++++++++++
         A control.h                           |       4 ++++
         A display.c                           |     119 +++++++++++++++++++++++++++++++
         A display.h                           |       2 ++
         D iomenu.c                            |     543 -------------------------------
         A iomenu.core                         |       0 
         A main.c                              |     123 +++++++++++++++++++++++++++++++
         A main.h                              |      29 +++++++++++++++++++++++++++++
         M utf8.c                              |      13 +++++--------
         M utf8.h                              |      12 ++++++------
       
       13 files changed, 556 insertions(+), 558 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       @@ -1,6 +1,6 @@
        CFLAGS = -std=c89 -Wpedantic -Wall -Wextra -g -D_POSIX_C_SOURCE=200809L
        
       -OBJ = iomenu.o utf8.o
       +OBJ = buffer.o control.o display.o main.o utf8.o
        
        all: iomenu
        
       @@ -15,3 +15,5 @@ install: iomenu
                cp *.1    $(PREFIX)/share/man/man1
                mkdir -p  $(PREFIX)/bin
                cp iomenu $(PREFIX)/bin
       +
       +.PHONY: all clean install
 (DIR) diff --git a/buffer.c b/buffer.c
       @@ -0,0 +1,93 @@
       +#include "buffer.h"
       +
       +static char *
       +read_line(FILE *fp)
       +{
       +        char *line;
       +        size_t len;
       +
       +        line = malloc(LINE_MAX + 1);
       +        if (!(fgets(line, LINE_MAX, fp))) {
       +                free(line);
       +                return NULL;
       +        }
       +
       +        len = strlen(line);
       +        if (len > 0 && line[len - 1] == '\n')
       +                line[len - 1] = '\0';
       +
       +        return (line);
       +}
       +
       +static int
       +match_line(char *line, char **tokv, int tokc)
       +{
       +        if (opt['#'] && line[0] == '#')
       +                return 2;
       +        while (tokc-- > 0)
       +                if (strstr(line, tokv[tokc]) == NULL)
       +                        return 0;
       +
       +        return 1;
       +}
       +
       +void
       +free_lines(void)
       +{
       +        if (linev) {
       +                for (; linec > 0; linec--)
       +                        free(linev[linec - 1]);
       +                free(linev);
       +        }
       +        if (matchv)
       +                free(matchv);
       +}
       +
       +void
       +read_stdin(void)
       +{
       +        int    size = 0;
       +
       +        while (1) {
       +                if (linec >= size) {
       +                        size += BUFSIZ;
       +                        linev  = realloc(linev,  sizeof (char **) * size);
       +                        matchv = realloc(matchv, sizeof (char **) * size);
       +                        if (!linev || !matchv)
       +                                die("realloc");
       +                }
       +                if ((linev[linec] = read_line(stdin)) == NULL)
       +                        break;
       +                linec++;
       +                matchc++;
       +        }
       +}
       +
       +void
       +filter(void)
       +{
       +        int tokc = 0;
       +        int n    = 0;
       +        int i;
       +        char **tokv = NULL;
       +        char *s;
       +        char buffer[sizeof (input)];
       +
       +        current = offset = next = 0;
       +        strcpy(buffer, input);
       +        for (s = strtok(buffer, " "); s; s = strtok(NULL, " "), tokc++) {
       +                if (tokc >= n) {
       +                        tokv = realloc(tokv, ++n * sizeof (*tokv));
       +                        if (tokv == NULL)
       +                                die("realloc");
       +                }
       +                tokv[tokc] = s;
       +        }
       +        matchc = 0;
       +        for (i = 0; i < linec; i++)
       +                if (match_line(linev[i], tokv, tokc))
       +                        matchv[matchc++] = linev[i];
       +        free(tokv);
       +        if (opt['#'] && matchv[current][0] == '#')
       +                move(+1);
       +}
 (DIR) diff --git a/buffer.h b/buffer.h
       @@ -0,0 +1,3 @@
       +void free_lines (void);
       +void read_stdin (void);
       +void filter(void);
 (DIR) diff --git a/control.c b/control.c
       @@ -0,0 +1,169 @@
       +#include "control.h"
       +
       +#define CTL(char) ((char) ^ 0x40)
       +#define ALT(char) ((char) + 0x80)
       +#define CSI(char) ((char) + 0x80 + 0x80)
       +
       +static size_t
       +width(char *s)
       +{
       +        int width = 0;
       +
       +        while (*s) {
       +                if (*s++ == '\t')
       +                        width += 8 - (width % 8);
       +                else
       +                        width++;
       +        }
       +
       +        return width;
       +}
       +
       +int
       +prev_page(int pos)
       +{
       +        int col, cols = ws.ws_col - MARGIN - 4;
       +
       +        pos -= pos > 0 ? 1 : 0;
       +        for (col = 0; pos > 0; pos--)
       +                if ((col += width(matchv[pos]) + 2) > cols)
       +                        return pos + 1;
       +        return pos;
       +}
       +
       +int
       +next_page(int pos)
       +{
       +        int col, cols = ws.ws_col - MARGIN - 4;
       +
       +        for (col = 0; pos < matchc; pos++)
       +                if ((col += width(matchv[pos]) + 2) > cols)
       +                        return pos;
       +        return pos;
       +}
       +
       +void
       +move(signed int sign)
       +{
       +        int i;
       +
       +        for (i = current + sign; 0 <= i && i < matchc; i += sign) {
       +                if (!opt['#'] || matchv[i][0] != '#') {
       +                        current = i;
       +                        break;
       +                }
       +        }
       +}
       +
       +static void
       +move_page(signed int sign)
       +{
       +        int i;
       +
       +        if (opt['l'] <= 0) {
       +                if (sign > 0) {
       +                        offset = current = next;
       +                        next   = next_page(next);
       +                } else if (sign < 0) {
       +                        next   = offset;
       +                        offset = current = prev_page(offset);
       +                }
       +        } else {
       +                i = current - current % rows + rows * sign;
       +                if (!(0 < i && i < matchc))
       +                        return;
       +                current = i - 1;
       +                move(+1);
       +        }
       +}
       +
       +static void
       +remove_word()
       +{
       +        int len;
       +        int 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();
       +}
       +
       +static void
       +add_char(char c)
       +{
       +        int len;
       +
       +        len = strlen(input);
       +        if (isprint(c)) {
       +                input[len]     = c;
       +                input[len + 1] = '\0';
       +        }
       +        filter();
       +}
       +
       +int
       +key(int k)
       +{
       +top:
       +        switch (k) {
       +        case CTL('C'):
       +                return EXIT_FAILURE;
       +        case CTL('U'):
       +                input[0] = '\0';
       +                filter();
       +                break;
       +        case CTL('W'):
       +                remove_word();
       +                break;
       +        case 127:
       +        case CTL('H'):  /* backspace */
       +                input[strlen(input) - 1] = '\0';
       +                filter();
       +                break;
       +        case CSI('A'):  /* up */
       +        case CTL('P'):
       +                move(-1);
       +                break;
       +        case CSI('B'):  /* down */
       +        case CTL('N'):
       +                move(+1);
       +                break;
       +        case CSI('5'):  /* page up */
       +                if (fgetc(stdin) != '~')
       +                        break;
       +                /* fallthrough */
       +        case ALT('v'):
       +                move_page(-1);
       +                break;
       +        case CSI('6'):  /* page down */
       +                if (fgetc(stdin) != '~')
       +                        break;
       +                /* fallthrough */
       +        case CTL('V'):
       +                move_page(+1);
       +                break;
       +        case CTL('I'):  /* tab */
       +                if (linec > 0)
       +                        strcpy(input, matchv[current]);
       +                filter();
       +                break;
       +        case CTL('J'):  /* enter */
       +        case CTL('M'):
       +                print_selection();
       +                return EXIT_SUCCESS;
       +        case ALT('['):
       +                k = CSI(fgetc(stdin));
       +                goto top;
       +        case 0x1b: /* escape / alt */
       +                k = ALT(fgetc(stdin));
       +                goto top;
       +        default:
       +                add_char((char) k);
       +        }
       +
       +        return CONTINUE;
       +}
 (DIR) diff --git a/control.h b/control.h
       @@ -0,0 +1,4 @@
       +int  prev_page (int);
       +int  next_page (int);
       +void move      (signed int);
       +int  key       (int);
 (DIR) diff --git a/display.c b/display.c
       @@ -0,0 +1,119 @@
       +#include "display.h"
       +
       +static char *
       +format(char *str, int cols)
       +{
       +        int   col = 0;
       +        long  rune = 0;
       +        char *fmt = formatted;
       +
       +        while (*str && col < cols) {
       +                if (*str == '\t') {
       +                        int t = 8 - col % 8;
       +                        while (t-- && col < cols) {
       +                                *fmt++ = ' ';
       +                                col++;
       +                        }
       +                        str++;
       +                } else if (utf8_to_rune(&rune, str) && rune_is_print(rune)) {
       +                        int i = utf8_len(str);
       +                        while (i--)
       +                                *fmt++ = *str++;
       +                        col++;
       +                } else {
       +                        *fmt++ = '?';
       +                        col++;
       +                        str++;
       +                }
       +        }
       +        *fmt = '\0';
       +
       +        return formatted;
       +}
       +
       +static void
       +print_lines(void)
       +{
       +        int printed = 0, i = current - current % rows;
       +
       +        for (; printed < rows && i < matchc; i++, printed++) {
       +                fprintf(stderr,
       +                        opt['#'] && matchv[i][0] == '#' ?
       +                        "\n\x1b[1m\x1b[K %s\x1b[m"      :
       +                        i == current                    ?
       +                        "\n\x1b[47;30m\x1b[K %s\x1b[m"      :
       +                        "\n\x1b[K %s",
       +                        format(matchv[i], ws.ws_col - 1)
       +                );
       +        }
       +        while (printed++ < rows)
       +                fputs("\n\x1b[K", stderr);
       +        fprintf(stderr, "\x1b[%dA\r\x1b[K", rows);
       +}
       +
       +static void
       +print_segments(void)
       +{
       +        int i;
       +
       +        if (current < offset) {
       +                next   = offset;
       +                offset = prev_page(offset);
       +        } else if (current >= next) {
       +                offset = next;
       +                next   = next_page(offset);
       +        }
       +        fprintf(stderr, "\r\x1b[K\x1b[%dC", MARGIN);
       +        fputs(offset > 0 ? "< " : "  ", stderr);
       +        for (i = offset; i < next && i < matchc; i++) {
       +                fprintf(stderr,
       +                        opt['#'] && matchv[i][0] == '#' ? "\x1b[1m %s \x1b[m" :
       +                        i == current ? "\x1b[7m %s \x1b[m" : " %s ",
       +                        format(matchv[i], ws.ws_col - 1)
       +                );
       +        }
       +        if (next < matchc)
       +                fprintf(stderr, "\x1b[%dC\b>", ws.ws_col - MARGIN);
       +        fputc('\r', stderr);
       +}
       +
       +void
       +print_screen(void)
       +{
       +        int cols = ws.ws_col - 1;
       +
       +        if (opt['l'] > 0)
       +                print_lines();
       +        else
       +                print_segments();
       +        if (*prompt) {
       +                format(prompt, cols - 2);
       +                fprintf(stderr, "\x1b[30;47m %s \x1b[m", formatted);
       +                cols -= strlen(formatted) + 2;
       +        }
       +        fputc(' ', stderr);
       +        fputs(format(input, cols), stderr);
       +        fflush(stderr);
       +}
       +
       +void
       +print_selection(void)
       +{
       +        char **match;
       +
       +        if (opt['#']) {
       +                match = matchv + current;
       +                while (--match >= matchv) {
       +                        if ((*match)[0] == '#') {
       +                                fputs(*match + 1, stdout);
       +                                break;
       +                        }
       +                }
       +                putchar('\t');
       +        }
       +        if (matchc == 0 || (opt['#'] && matchv[current][0] == '#'))
       +                puts(input);
       +        else
       +                puts(matchv[current]);
       +        fputs("\r\x1b[K", stderr);
       +}
 (DIR) diff --git a/display.h b/display.h
       @@ -0,0 +1,2 @@
       +void print_screen    (void);
       +void print_selection (void);
 (DIR) diff --git a/iomenu.c b/iomenu.c
       @@ -1,543 +0,0 @@
       -#include <ctype.h>
       -#include <fcntl.h>
       -#include <limits.h>
       -#include <locale.h>
       -#include <signal.h>
       -#include <stdio.h>
       -#include <stdlib.h>
       -#include <string.h>
       -#include <termios.h>
       -#include <unistd.h>
       -
       -#include <sys/ioctl.h>
       -
       -#include "utf8.h"
       -
       -#ifndef SIGWINCH
       -#define SIGWINCH 28
       -#endif
       -#define CONTINUE 2   /* as opposed to EXIT_SUCCESS and EXIT_FAILURE */
       -#define MARGIN   30  /* space for the input before the horizontal list */
       -
       -#define  CTL(char) (char ^ 0x40)
       -#define  ALT(char) (char + 0x80)
       -#define  CSI(char) (char + 0x80 + 0x80)
       -#define  MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
       -
       -static struct winsize ws;
       -static struct termios termios;
       -static int            ttyfd;
       -static int            current = 0, offset = 0, next = 0;
       -static int            linec = 0,      matchc = 0;
       -static char         **linev = NULL, **matchv = NULL;
       -static char           input[LINE_MAX], formatted[LINE_MAX * 8];
       -static int            opt[128], rows = 0;
       -static char          *prompt = "";
       -
       -static void
       -free_lines(void)
       -{
       -        if (linev) {
       -                for (; linec > 0; linec--)
       -                        free(linev[linec - 1]);
       -                free(linev);
       -        }
       -        if (matchv)
       -                free(matchv);
       -}
       -
       -static void
       -die(const char *s)
       -{
       -        tcsetattr(ttyfd, TCSANOW, &termios);
       -        close(ttyfd);
       -        free_lines();
       -        perror(s);
       -        exit(EXIT_FAILURE);
       -}
       -
       -static char *
       -read_line(FILE *fp)
       -{
       -        char *line;
       -        size_t len;
       -
       -        line = malloc(LINE_MAX + 1);
       -        if (!(fgets(line, LINE_MAX, fp))) {
       -                free(line);
       -                return NULL;
       -        }
       -
       -        len = strlen(line);
       -        if (len > 0 && line[len - 1] == '\n')
       -                line[len - 1] = '\0';
       -
       -        return (line);
       -}
       -
       -static void
       -read_stdin(void)
       -{
       -        int    size = 0;
       -
       -        while (1) {
       -                if (linec >= size) {
       -                        size += BUFSIZ;
       -                        linev  = realloc(linev,  sizeof (char **) * size);
       -                        matchv = realloc(matchv, sizeof (char **) * size);
       -                        if (!linev || !matchv)
       -                                die("realloc");
       -                }
       -                if ((linev[linec] = read_line(stdin)) == NULL)
       -                        break;
       -                linec++;
       -                matchc++;
       -        }
       -}
       -
       -static void
       -set_terminal(void)
       -{
       -        struct termios new;
       -
       -        /* save currentsor postition */
       -        fputs("\x1b[s", stderr);
       -
       -        /* save attributes to `termios` */
       -        if (tcgetattr(ttyfd, &termios) < 0 || tcgetattr(ttyfd, &new) < 0) {
       -                perror("tcgetattr");
       -                exit(EXIT_FAILURE);
       -        }
       -
       -        /* change to raw mode */
       -        new.c_lflag &= ~(ICANON | ECHO | IGNBRK | IEXTEN | ISIG);
       -        tcsetattr(ttyfd, TCSANOW, &new);
       -}
       -
       -static void
       -reset_terminal(void)
       -{
       -        int i;
       -
       -        /* clear terminal */
       -        for (i = 0; i < rows + 1; i++)
       -                fputs("\r\x1b[K\n", stderr);
       -
       -        /* reset currentsor position */
       -        fputs("\x1b[u", stderr);
       -
       -        tcsetattr(ttyfd, TCSANOW, &termios);
       -}
       -
       -static size_t
       -width(char *s)
       -{
       -        int width = 0;
       -
       -        while (*s) {
       -                if (*s++ == '\t')
       -                        width += 8 - (width % 8);
       -                else
       -                        width++;
       -        }
       -
       -        return width;
       -}
       -
       -static int
       -prev_page(int pos)
       -{
       -        int col, cols = ws.ws_col - MARGIN - 4;
       -
       -        pos -= pos > 0 ? 1 : 0;
       -        for (col = 0; pos > 0; pos--)
       -                if ((col += width(matchv[pos]) + 2) > cols)
       -                        return pos + 1;
       -        return pos;
       -}
       -
       -static int
       -next_page(int pos)
       -{
       -        int col, cols = ws.ws_col - MARGIN - 4;
       -
       -        for (col = 0; pos < matchc; pos++)
       -                if ((col += width(matchv[pos]) + 2) > cols)
       -                        return pos;
       -        return pos;
       -}
       -
       -static void
       -move(signed int sign)
       -{
       -        int i;
       -
       -        for (i = current + sign; 0 <= i && i < matchc; i += sign) {
       -                if (!opt['#'] || matchv[i][0] != '#') {
       -                        current = i;
       -                        break;
       -                }
       -        }
       -}
       -
       -static void
       -move_page(signed int sign)
       -{
       -        int i;
       -
       -        if (opt['l'] <= 0) {
       -                if (sign > 0) {
       -                        offset = current = next;
       -                        next   = next_page(next);
       -                } else if (sign < 0) {
       -                        next   = offset;
       -                        offset = current = prev_page(offset);
       -                }
       -        } else {
       -                i = current - current % rows + rows * sign;
       -                if (!(0 < i && i < matchc))
       -                        return;
       -                current = i - 1;
       -                move(+1);
       -        }
       -}
       -
       -static char *
       -format(char *str, int cols)
       -{
       -        int   col = 0;
       -        long  rune = 0;
       -        char *fmt = formatted;
       -
       -        while (*str && col < cols) {
       -                if (*str == '\t') {
       -                        int t = 8 - col % 8;
       -                        while (t-- && col < cols) {
       -                                *fmt++ = ' ';
       -                                col++;
       -                        }
       -                        str++;
       -                } else if (utf8_to_rune(&rune, str) && rune_is_print(rune)) {
       -                        int i = utf8_len(str);
       -                        while (i--)
       -                                *fmt++ = *str++;
       -                        col++;
       -                } else {
       -                        *fmt++ = '?';
       -                        col++;
       -                        str++;
       -                }
       -        }
       -        *fmt = '\0';
       -
       -        return formatted;
       -}
       -
       -static void
       -print_lines(void)
       -{
       -        int printed = 0, i = current - current % rows;
       -
       -        for (; printed < rows && i < matchc; i++, printed++) {
       -                fprintf(stderr,
       -                        opt['#'] && matchv[i][0] == '#' ?
       -                        "\n\x1b[1m\x1b[K %s\x1b[m"      :
       -                        i == current                    ?
       -                        "\n\x1b[47;30m\x1b[K %s\x1b[m"      :
       -                        "\n\x1b[K %s",
       -                        format(matchv[i], ws.ws_col - 1)
       -                );
       -        }
       -        while (printed++ < rows)
       -                fputs("\n\x1b[K", stderr);
       -        fprintf(stderr, "\x1b[%dA\r\x1b[K", rows);
       -}
       -
       -static void
       -print_segments(void)
       -{
       -        int i;
       -
       -        if (current < offset) {
       -                next   = offset;
       -                offset = prev_page(offset);
       -        } else if (current >= next) {
       -                offset = next;
       -                next   = next_page(offset);
       -        }
       -        fprintf(stderr, "\r\x1b[K\x1b[%dC", MARGIN);
       -        fputs(offset > 0 ? "< " : "  ", stderr);
       -        for (i = offset; i < next && i < matchc; i++) {
       -                fprintf(stderr,
       -                        opt['#'] && matchv[i][0] == '#' ? "\x1b[1m %s \x1b[m" :
       -                        i == current ? "\x1b[7m %s \x1b[m" : " %s ",
       -                        format(matchv[i], ws.ws_col - 1)
       -                );
       -        }
       -        if (next < matchc)
       -                fprintf(stderr, "\x1b[%dC\b>", ws.ws_col - MARGIN);
       -        fputc('\r', stderr);
       -}
       -
       -static void
       -print_screen(void)
       -{
       -        int cols = ws.ws_col - 1;
       -
       -        if (opt['l'] > 0)
       -                print_lines();
       -        else
       -                print_segments();
       -        if (*prompt) {
       -                format(prompt, cols - 2);
       -                fprintf(stderr, "\x1b[30;47m %s \x1b[m", formatted);
       -                cols -= strlen(formatted) + 2;
       -        }
       -        fputc(' ', stderr);
       -        fputs(format(input, cols), stderr);
       -        fflush(stderr);
       -}
       -
       -static int
       -match_line(char *line, char **tokv, int tokc)
       -{
       -        if (opt['#'] && line[0] == '#')
       -                return 2;
       -        while (tokc-- > 0)
       -                if (strstr(line, tokv[tokc]) == NULL)
       -                        return 0;
       -
       -        return 1;
       -}
       -
       -static void
       -filter(void)
       -{
       -        int tokc = 0;
       -        int n    = 0;
       -        int i;
       -        char **tokv = NULL;
       -        char *s;
       -        char buffer[sizeof (input)];
       -
       -        current = offset = next = 0;
       -        strcpy(buffer, input);
       -        for (s = strtok(buffer, " "); s; s = strtok(NULL, " "), tokc++) {
       -                if (tokc >= n) {
       -                        tokv = realloc(tokv, ++n * sizeof (*tokv));
       -                        if (tokv == NULL)
       -                                die("realloc");
       -                }
       -                tokv[tokc] = s;
       -        }
       -        matchc = 0;
       -        for (i = 0; i < linec; i++)
       -                if (match_line(linev[i], tokv, tokc))
       -                        matchv[matchc++] = linev[i];
       -        free(tokv);
       -        if (opt['#'] && matchv[current][0] == '#')
       -                move(+1);
       -}
       -
       -static void
       -remove_word()
       -{
       -        int len;
       -        int 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();
       -}
       -
       -static void
       -add_char(char key)
       -{
       -        int len;
       -
       -        len = strlen(input);
       -        if (isprint(key)) {
       -                input[len]     = key;
       -                input[len + 1] = '\0';
       -        }
       -        filter();
       -}
       -
       -static void
       -print_selection(void)
       -{
       -        char **match;
       -
       -        if (opt['#']) {
       -                match = matchv + current;
       -                while (--match >= matchv) {
       -                        if ((*match)[0] == '#') {
       -                                fputs(*match + 1, stdout);
       -                                break;
       -                        }
       -                }
       -                putchar('\t');
       -        }
       -        if (matchc == 0 || (opt['#'] && matchv[current][0] == '#'))
       -                puts(input);
       -        else
       -                puts(matchv[current]);
       -        fputs("\r\x1b[K", stderr);
       -}
       -
       -static int
       -key(int key)
       -{
       -top:
       -        switch (key) {
       -
       -        case CTL('C'):
       -                return EXIT_FAILURE;
       -
       -        case CTL('U'):
       -                input[0] = '\0';
       -                filter();
       -                break;
       -
       -        case CTL('W'):
       -                remove_word();
       -                break;
       -
       -        case 127:
       -        case CTL('H'):  /* backspace */
       -                input[strlen(input) - 1] = '\0';
       -                filter();
       -                break;
       -
       -        case CSI('A'):  /* up */
       -        case CTL('P'):
       -                move(-1);
       -                break;
       -
       -        case CSI('B'):  /* down */
       -        case CTL('N'):
       -                move(+1);
       -                break;
       -
       -        case CSI('5'):  /* page up */
       -                if (fgetc(stdin) != '~')
       -                        break;
       -                /* fallthrough */
       -
       -        case ALT('v'):
       -                move_page(-1);
       -                break;
       -
       -        case CSI('6'):  /* page down */
       -                if (fgetc(stdin) != '~')
       -                        break;
       -                /* fallthrough */
       -
       -        case CTL('V'):
       -                move_page(+1);
       -                break;
       -
       -        case CTL('I'):  /* tab */
       -                if (linec > 0)
       -                        strcpy(input, matchv[current]);
       -                filter();
       -                break;
       -
       -        case CTL('J'):  /* enter */
       -        case CTL('M'):
       -                print_selection();
       -                return EXIT_SUCCESS;
       -
       -        case ALT('['):
       -                key = CSI(fgetc(stdin));
       -                goto top;
       -
       -        case 0x1b: /* escape / alt */
       -                key = ALT(fgetc(stdin));
       -                goto top;
       -
       -        default:
       -                add_char((char) key);
       -        }
       -
       -        return CONTINUE;
       -}
       -
       -static void
       -sigwinch()
       -{
       -        if (ioctl(ttyfd, TIOCGWINSZ, &ws) < 0)
       -                die("ioctl");
       -        rows = MIN(opt['l'], ws.ws_row - 1);
       -        print_screen();
       -        signal(SIGWINCH, sigwinch);
       -}
       -
       -static void
       -usage(void)
       -{
       -        fputs("iomenu [-#] [-l lines] [-p prompt]\n", stderr);
       -        exit(EXIT_FAILURE);
       -}
       -
       -static void
       -parse_opt(int argc, char *argv[])
       -{
       -        memset(opt, 0, 128 * sizeof (int));
       -        opt['l'] = 255;
       -        for (argv++, argc--; argc > 0; argv++, argc--) {
       -                if (argv[0][0] != '-')
       -                        usage();
       -                switch ((*argv)[1]) {
       -                case 'l':
       -                        if (!--argc || sscanf(*++argv, "%d", &opt['l']) <= 0)
       -                                usage();
       -                        break;
       -                case 'p':
       -                        if (!--argc)
       -                                usage();
       -                        prompt = *++argv;
       -                        break;
       -                case '#':
       -                        opt['#'] = 1;
       -                        break;
       -                case 's':
       -                        if (!--argc)
       -                                usage();
       -                        opt['s'] = (int) **++argv;
       -                        break;
       -                default:
       -                        usage();
       -                }
       -        }
       -}
       -
       -int
       -main(int argc, char *argv[])
       -{
       -        int exit_code;
       -
       -        parse_opt(argc, argv);
       -        read_stdin();
       -        filter();
       -        if (!freopen("/dev/tty", "r", stdin))
       -                die("freopen /dev/tty");
       -        if (!freopen("/dev/tty", "w", stderr))
       -                die("freopen /dev/tty");
       -        ttyfd =  open("/dev/tty", O_RDWR);
       -        set_terminal();
       -        sigwinch();
       -        input[0] = '\0';
       -        while ((exit_code = key(fgetc(stdin))) == CONTINUE)
       -                print_screen();
       -        print_screen();
       -        reset_terminal();
       -        close(ttyfd);
       -        free_lines();
       -
       -        return exit_code;
       -}
 (DIR) diff --git a/iomenu.core b/iomenu.core
       Binary files differ.
 (DIR) diff --git a/main.c b/main.c
       @@ -0,0 +1,123 @@
       +#include "main.h"
       +
       +static struct termios termios;
       +static int            ttyfd;
       +
       +void
       +die(const char *s)
       +{
       +        tcsetattr(ttyfd, TCSANOW, &termios);
       +        close(ttyfd);
       +        free_lines();
       +        perror(s);
       +        exit(EXIT_FAILURE);
       +}
       +
       +static void
       +set_terminal(void)
       +{
       +        struct termios new;
       +
       +        /* save currentsor postition */
       +        fputs("\x1b[s", stderr);
       +
       +        /* save attributes to `termios` */
       +        if (tcgetattr(ttyfd, &termios) < 0 || tcgetattr(ttyfd, &new) < 0) {
       +                perror("tcgetattr");
       +                exit(EXIT_FAILURE);
       +        }
       +
       +        /* change to raw mode */
       +        new.c_lflag &= ~(ICANON | ECHO | IGNBRK | IEXTEN | ISIG);
       +        tcsetattr(ttyfd, TCSANOW, &new);
       +}
       +
       +static void
       +reset_terminal(void)
       +{
       +        int i;
       +
       +        /* clear terminal */
       +        for (i = 0; i < rows + 1; i++)
       +                fputs("\r\x1b[K\n", stderr);
       +
       +        /* reset currentsor position */
       +        fputs("\x1b[u", stderr);
       +
       +        tcsetattr(ttyfd, TCSANOW, &termios);
       +}
       +
       +static void
       +sigwinch()
       +{
       +        if (ioctl(ttyfd, TIOCGWINSZ, &ws) < 0)
       +                die("ioctl");
       +        rows = MIN(opt['l'], ws.ws_row - 1);
       +        print_screen();
       +        signal(SIGWINCH, sigwinch);
       +}
       +
       +static void
       +usage(void)
       +{
       +        fputs("iomenu [-#] [-l lines] [-p prompt]\n", stderr);
       +        exit(EXIT_FAILURE);
       +}
       +
       +static void
       +parse_opt(int argc, char *argv[])
       +{
       +        memset(opt, 0, 128 * sizeof (int));
       +        opt['l'] = 255;
       +        for (argv++, argc--; argc > 0; argv++, argc--) {
       +                if (argv[0][0] != '-')
       +                        usage();
       +                switch ((*argv)[1]) {
       +                case 'l':
       +                        if (!--argc || sscanf(*++argv, "%d", &opt['l']) <= 0)
       +                                usage();
       +                        break;
       +                case 'p':
       +                        if (!--argc)
       +                                usage();
       +                        prompt = *++argv;
       +                        break;
       +                case '#':
       +                        opt['#'] = 1;
       +                        break;
       +                case 's':
       +                        if (!--argc)
       +                                usage();
       +                        opt['s'] = (int) **++argv;
       +                        break;
       +                default:
       +                        usage();
       +                }
       +        }
       +}
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        int exit_code;
       +
       +        parse_opt(argc, argv);
       +        read_stdin();
       +        filter();
       +        if (!freopen("/dev/tty", "r", stdin))
       +                die("freopen /dev/tty");
       +        if (!freopen("/dev/tty", "w", stderr))
       +                die("freopen /dev/tty");
       +        ttyfd =  open("/dev/tty", O_RDWR);
       +        set_terminal();
       +        sigwinch();
       +        input[0] = '\0';
       +        while ((exit_code = key(fgetc(stdin))) == CONTINUE)
       +                print_screen();
       +        print_screen();
       +        reset_terminal();
       +        close(ttyfd);
       +        free_lines();
       +
       +        return exit_code;
       +}
 (DIR) diff --git a/main.h b/main.h
       @@ -0,0 +1,29 @@
       +#ifndef SIGWINCH
       +#define SIGWINCH 28
       +#endif
       +
       +#define CONTINUE (EXIT_SUCCESS + EXIT_FAILURE + 1)
       +#define MARGIN   30
       +
       +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
       +
       +winsize   ws;
       +char    **linev   = NULL;
       +int       linec   = 0;
       +char    **matchv  = NULL;
       +int       matchc  = 0;
       +char     *prompt  = "";
       +char      input[LINE_MAX];
       +char      formatted[LINE_MAX * 8];
       +int       current = 0;
       +int       offset  = 0;
       +int       next    = 0;
       +int       opt[128];
       +int       rows    = 0;
       +
       +size_t utf8_len        (char *);
       +size_t rune_len        (long);
       +size_t utf8_to_rune    (long *, char *);
       +int    utf8_is_unicode (long);
       +int    utf8_check      (char *);
       +int    utf8_is_print   (long);
 (DIR) diff --git a/utf8.c b/utf8.c
       @@ -1,3 +1,5 @@
       +#include "utf8.h"
       +
        /*
         * ASCII all have a leading '0' byte:
         *
       @@ -28,7 +30,6 @@
        
        #include "utf8.h"
        
       -
        /*
         * Return the number of bytes in rune for the `n` next char in `s`,
         * or 0 if ti is misencoded.
       @@ -56,20 +57,18 @@ utf8_len(char *s)
                return len;
        }
        
       -
        /*
         * Return the number of bytes required to encode `rune` into UTF-8, or
         * 0 if rune is too long.
         */
        size_t
       -rune_len(long r)
       +utf8_rune_len(long r)
        {
                return (r <= 0x0000007f) ? 1 : (r <= 0x000007ff) ? 2 :
                       (r <= 0x0000ffff) ? 3 : (r <= 0x001fffff) ? 4 :
                       (r <= 0x03ffffff) ? 5 : (r <= 0x7fffffff) ? 6 : 0;
        }
        
       -
        /*
         * Sets 'r' to a rune corresponding to the firsts 'n' bytes of 's'.
         *
       @@ -92,13 +91,12 @@ utf8_to_rune(long *r, char *s)
                        *r = (*r << 6) | (*s++ & 0x3f);  /* 10xxxxxx */
        
                /* overlong sequences */
       -        if (rune_len(*r) != len)
       +        if (utf8_rune_len(*r) != len)
                        return 0;
        
                return len;
        }
        
       -
        /*
         * Returns 1 if the rune is a valid unicode code point and 0 if not.
         */
       @@ -119,7 +117,6 @@ rune_is_unicode(long r)
                );
        }
        
       -
        /*
         * Return 1 if '*s' is correctly encoded in UTF-8 with allowed Unicode
         * code points.
       @@ -146,7 +143,7 @@ utf8_check(char *s)
         * Return 1 if the rune is a printable character, and 0 otherwise.
         */
        int
       -rune_is_print(long r)
       +utf8_is_print(long r)
        {
                return (0x1f < r && r != 0x7f && r < 0x80) || 0x9f < r;
        }
 (DIR) diff --git a/utf8.h b/utf8.h
       @@ -1,6 +1,6 @@
       -size_t utf8_len(char *);
       -size_t rune_len(long);
       -size_t utf8_to_rune(long *, char *);
       -int    utf8_is_unicode(long);
       -int    utf8_check(char *);
       -int    rune_is_print(long);
       +size_t utf8_len        (char *);
       +size_t rune_len        (long);
       +size_t utf8_to_rune    (long *, char *);
       +int    utf8_is_unicode (long);
       +int    utf8_check      (char *);
       +int    utf8_is_print   (long);