URI: 
       use utf8_col() instead of format() which used a buffer, remove -p flag - iomenu - interactive terminal-based selection menu
  HTML git clone git://bitreich.org/iomenu git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/iomenu
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
   DIR commit 1a73e9911476f01c8a4548f020b8def4e62bfe2b
   DIR parent 00bb578f57d039ac6e6c2ce2835da575c78c76f3
  HTML Author: Josuah Demangeon <mail@josuah.net>
       Date:   Thu, 18 Jan 2018 23:43:01 +0100
       
       use utf8_col() instead of format() which used a buffer, remove -p flag
       
       Diffstat:
         M iomenu.1                            |      51 +++++++++++++++++++++++++++----
         M iomenu.c                            |     149 ++++++++++++-------------------
         M test.c                              |       6 +++---
         M utf8.c                              |       4 ++--
       
       4 files changed, 107 insertions(+), 103 deletions(-)
       ---
   DIR diff --git a/iomenu.1 b/iomenu.1
       @@ -1,81 +1,120 @@
        .Dd aug 21, 2017
        .Dt IOMENU 1
        .Os
       +.
       +.
        .Sh NAME
       +.
        .Nm iomenu
        .Nd interactive selection menu
       +.
       +.
        .Sh SYNOPSIS
       +.
        .Nm
        .Op Fl #
       -.Op Fl p Ar prompt
       +.
       +.
        .Sh DESCRIPTION
       +.
        .Nm
        is an interactive filtering and selection tool for the terminal.
       +.
        .Pp
        It reads lines from standard input, and prompt for a selection.
        The selected line is printed to standard output.
       -.Bl -tag -width XXXXXXXXXXXXXXXX
       -.It Fl p Ar prompt
       -Set the prompt to display at the beginning of the input to
       -.Ar prompt .
       +.
       +.Bl -tag -width 6n
       +.
        .It Fl #
        If a line starts with
        .Li # ,
        .Nm
        will interprete it as a header, which always matches, and can not be
        printed.
       -.Pp
       +.
       +.
        .Sh KEY BINDINGS
       +.
        An active selection is highlighted, and can be controlled with keybindings.
        As printable keys are entered, the lines are filtered to match each
        word from the input.
       +.
        .Bl -tag -width XXXXXXXXXXXXXXX
       +.
        .It Ic Up Ns , Ic Down Ns , Ic Ctrl + p Ns , Ic Ctrl + n
        Move selection to the previous/next item.
       +.
        .It Ic PageUp Ns , Ic PageDown Ns , Ic Alt + v Ns , Ic Ctrl + v
        Move one page up or down.
       +.
        .It Ic Ctrl + m Ns , Ic Ctrl + j Ns , Ic Enter
        Print the selection to the standard output, and exit 0.
       +.
        .It Ic Ctrl + h Ns , Ic Bakcspace
        Remove last character from current input.
       +.
        .It Ic Ctrl + w
        Remove last word from current input.
       +.
        .It Ic Ctrl + u
        Remove the whole input string.
       +.
        .It Ic Ctrl + i Ns , Ic Tab
        Fill the input with current selection.
       +.
        .El
       +.
       +.
        .Sh EXIT STATUS
       +.
        .Ex -std
       +.
       +.
        .Sh EXAMPLES
       +.
        Go to a subdirectory:
       +.
        .Bd -literal -offset XX
        cd "$(find . -type d | iomenu)"
        .Ed
       +.
        .Pp
        Edit a file located in
        .Ev HOME :
       +.
        .Bd -literal -offset XX
        $EDITOR "$(find "$HOME" -type f | iomenu)"
        .Ed
       +.
        .Pp
        Play an audio file:
       +.
        .Bd -literal -offset XX
        mplayer "$(find ~/Music | iomenu)"
        .Ed
       +.
        .Pp
        Select a background job to attach to:
       +.
        .Bd -literal -offset XX
        fg "%$(jobs | iomenu | cut -c 2)"
        .Ed
       +.
        .Pp
        Filter "ps" output and print a process ID
        .Bd -literal -offset XX
        { printf '#'; ps ax; } | iomenu -# | sed -r 's/ *([0-9]*).*/\1/'
        .Ed
       +.
       +.
        .Sh SEE ALSO
       +.
        .Xr dmenu 1 ,
        .Xr slmenu 1 ,
        .Xr vis-menu 1
       +.
       +.
        .Sh AUTORS
       +.
        .An Josuah Demangeon Aq Mt mail@josuah.net
   DIR diff --git a/iomenu.c b/iomenu.c
       @@ -31,7 +31,6 @@ char                        **linev = NULL, **matchv = NULL;
        char                        input[LINE_MAX], formatted[LINE_MAX * 8];
        
        int                        flag_hs = 0;
       -char                        *flag_p = "";
        
        static char *
        io_strstr(const char *str1, const char *str2)
       @@ -39,7 +38,7 @@ io_strstr(const char *str1, const char *str2)
                const char        *s1;
                const char        *s2;
        
       -        while (1) {
       +        for (;;) {
                        s1 = str1;
                        s2 = str2;
                        while (*s1 != '\0' && tolower(*s1) == tolower(*s2))
       @@ -73,7 +72,7 @@ match_line(char *line, char **tokv)
         * Free the structures, reset the terminal state and exit with an error message.
         */
        static void
       -die(const char *s)
       +err(const char *s)
        {
                tcsetattr(ttyfd, TCSANOW, &termios);
                close(ttyfd);
       @@ -96,10 +95,10 @@ split_lines(char *buf)
                linec = 1;
                for (b = buf; (b = strchr(b, '\n')) != NULL && b[1] != '\0'; b++)
                        linec++;
       -        if ((lv = linev = calloc(linec + 1, sizeof (char **))) == NULL)
       -                die("calloc");
       -        if ((mv = matchv = calloc(linec + 1, sizeof (char **))) == NULL)
       -                die("calloc");
       +        if ((lv = linev = calloc(linec + 1, sizeof(char **))) == NULL)
       +                err("calloc");
       +        if ((mv = matchv = calloc(linec + 1, sizeof(char **))) == NULL)
       +                err("calloc");
                *mv = *lv = b = buf;
                while ((b = strchr(b, '\n')) != NULL) {
                        *b = '\0';
       @@ -120,13 +119,13 @@ read_stdin(void)
                size = BUFSIZ;
                off = 0;
                if ((buf = malloc(size)) == NULL)
       -                die("malloc");
       +                err("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)
       -                                die("realloc");
       +                                err("realloc");
                        }
                }
                buf[off] = '\0';
       @@ -134,14 +133,14 @@ read_stdin(void)
        }
        
        static void
       -move(signed int sign)
       +move(int direction)
        {
       -        extern        char        **matchv;
       -        extern        int          matchc;
       +        extern char        **matchv;
       +        extern int        matchc;
        
                int        i;
        
       -        for (i = cur + sign; 0 <= i && i < matchc; i += sign) {
       +        for (i = cur + direction; 0 <= i && i < matchc; i += direction) {
                        if (!flag_hs || matchv[i][0] != '#') {
                                cur = i;
                                break;
       @@ -171,7 +170,7 @@ filter(int searchc, char **searchv)
                extern int        matchc, cur;
        
                int        n;
       -        char        *tokv[sizeof(input) / 2 * sizeof(char *) + sizeof(NULL)];
       +        char        *tokv[sizeof(input) * sizeof(char *) + sizeof(NULL)];
                char        *s, buf[sizeof(input)];
        
                strncpy(buf, input, sizeof(input));
       @@ -329,71 +328,35 @@ top:
                return 1;
        }
        
       -static char *
       -format(char *str, int col)
       -{
       -        extern struct winsize        ws;
       -        extern char                formatted[LINE_MAX * 8];
       -
       -        int        c, n, w;
       -        long        rune = 0;
       -        char        *fmt;
       -
       -        fmt = formatted;
       -        for (c = 0; *str != '\0' && c < col; ) {
       -                if (*str == '\t') {
       -                        int t = 8 - c % 8;
       -                        while (t-- && c < col) {
       -                                *fmt++ = ' ';
       -                                c++;
       -                        }
       -                        str++;
       -                } else if ((n = utf8_torune(&rune, str)) > 0 &&
       -                    (w = utf8_wcwidth(rune)) > 0) {
       -                        while (n--)
       -                                *fmt++ = *str++;
       -                        c += w;
       -                } else {
       -                        *fmt++ = '?';
       -                        str += n;
       -                        c ++;
       -                }
       -        }
       -        *fmt = '\0';
       -
       -        return formatted;
       -}
       -
        static void
        print_line(char *line, int highlight)
        {
                extern        struct        winsize ws;
        
       -        if (flag_hs && line[0] == '#') {
       -                format(line + 1, ws.ws_col - 1);
       -                fprintf(stderr, "\n\x1b[1m %s\x1b[m", formatted);
       -        } else if (highlight) {
       -                format(line, ws.ws_col - 1);
       -                fprintf(stderr, "\n\x1b[47;30m\x1b[K %s\x1b[m", formatted);
       -        } else {
       -                format(line, ws.ws_col - 1);
       -                fprintf(stderr, "\n %s", formatted);
       -        }
       +        if (flag_hs && line[0] == '#')
       +                fprintf(stderr, "\n\x1b[1m\r%.*s\x1b[m",
       +                    utf8_col(line + 1, ws.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
       +                fprintf(stderr, "\n%.*s",
       +                    utf8_col(line, ws.ws_col, 0), line);
        }
        
        static void
        print_screen(void)
        {
                extern struct winsize        ws;
       -        extern char                **matchv, *flag_p, input[LINE_MAX];
       +        extern char                **matchv, input[LINE_MAX];
                extern int                matchc;
        
                char        **m;
       -        int          p, i, cols, rows;
       +        int          p, i, c, cols, rows;
        
       -        cols = ws.ws_col - 1;
       -        rows = ws.ws_row - 1;
       -        p = 0;
       +        cols = ws.ws_col;
       +        rows = ws.ws_row - 1;        /* keep one line for user input */
       +        p = c = 0;
                i = cur - cur % rows;
                m = matchv + i;
                fputs("\x1b[2J", stderr);
       @@ -402,13 +365,7 @@ print_screen(void)
                        p++, i++, m++;
                }
                fputs("\x1b[H", stderr);
       -        if (*flag_p) {
       -                format(flag_p, cols - 2);
       -                fprintf(stderr, "\x1b[30;47m %s \x1b[m", formatted);
       -                cols -= strlen(formatted) + 2;
       -        }
       -        fputc(' ', stderr);
       -        fputs(format(input, cols), stderr);
       +        fprintf(stderr, "%.*s", utf8_col(input, cols, c), input);
                fflush(stderr);
        }
        
       @@ -448,7 +405,7 @@ sigwinch()
                extern struct winsize        ws;
        
                if (ioctl(ttyfd, TIOCGWINSZ, &ws) < 0)
       -                die("ioctl");
       +                err("ioctl");
                print_screen();
                signal(SIGWINCH, sigwinch);
        }
       @@ -456,7 +413,7 @@ sigwinch()
        static void
        usage(void)
        {
       -        fputs("usage: iomenu [-#] [-p flag_p]\n", stderr);
       +        fputs("usage: iomenu [-#]\n", stderr);
                exit(EXIT_FAILURE);
        }
        
       @@ -466,17 +423,10 @@ usage(void)
        static void
        parse_opt(int argc, char *argv[])
        {
       -        extern char        *flag_p;
       -
                for (argv++, argc--; argc > 0; argv++, argc--) {
                        if (argv[0][0] != '-')
                                usage();
                        switch ((*argv)[1]) {
       -                case 'p':
       -                        if (!--argc)
       -                                usage();
       -                        flag_p = *++argv;
       -                        break;
                        case '#':
                                flag_hs = 1;
                                break;
       @@ -486,6 +436,26 @@ parse_opt(int argc, char *argv[])
                }
        }
        
       +void
       +init(void)
       +{
       +        extern char        input[LINE_MAX];
       +
       +        input[0] = '\0';
       +        read_stdin();
       +        filter(linec, linev);
       +
       +        if (freopen("/dev/tty", "r", stdin) == NULL)
       +                err("freopen /dev/tty");
       +        if (freopen("/dev/tty", "w", stderr) == NULL)
       +                err("freopen /dev/tty");
       +        if ((ttyfd = open("/dev/tty", O_RDWR)) < 0)
       +                err("open /dev/tty");
       +
       +        set_terminal();
       +        sigwinch();
       +}
       +
        /*
         * Read stdin in a buffer, filling a table of lines, then re-open stdin to
         * /dev/tty for an interactive (raw) session to let the user filter and select
       @@ -494,23 +464,18 @@ parse_opt(int argc, char *argv[])
        int
        main(int argc, char *argv[])
        {
       -        extern char        input[LINE_MAX];
       -
                int                exit_code;
        
                parse_opt(argc, argv);
       -        read_stdin();
       -        filter(linec, linev);
       -        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';
       +        init();
       +
       +#ifdef __OpenBSD__
       +        pledge("stdio tty", NULL);
       +#endif
       +
                while ((exit_code = key(fgetc(stdin))) > 0)
                        print_screen();
       +
                print_screen();
                reset_terminal();
                close(ttyfd);
   DIR diff --git a/test.c b/test.c
       @@ -6,10 +6,10 @@ int
        main(void)
        {
                int c, col, o, off;
       -        char s[] = "        浪漫的夢想";
       +        char s[] = "\t\t浪漫的夢想";
        
       -        for (off = 0; off < 10; off++) {
       -                for (col = off + 1; col < 25; col++) {
       +        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);
   DIR diff --git a/utf8.c b/utf8.c
       @@ -173,14 +173,14 @@ utf8_col(char *str, int col, int off)
                long rune;
                char *pos, *s;
        
       -        for (s = str; off < col;) {
       +        for (s = str; off <= col;) {
                        pos = s;
                        if (*s == '\0')
                                break;
        
                        s += utf8_torune(&rune, s);
                        if (rune == '\t')
       -                        off += 7 - (off) % 8;
       +                        off += 8 - (off % 8);
                        else
                                off += utf8_wcwidth(rune);
                }