Home
       import from repo.or.cz (sadly) - 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 d0e21509afe9e6ebf8e3f0e75ce31d9799905c7a
  HTML Author: Josuah Demangeon⠠⠵ <mail@josuah.net>
       Date:   Sat, 11 Mar 2017 11:18:03 +0100
       
       import from repo.or.cz (sadly)
       
       Diffstat:
         A LICENSE                             |      21 +++++++++++++++++++++
         A Makefile                            |      22 ++++++++++++++++++++++
         A README                              |      90 +++++++++++++++++++++++++++++++
         A TODO                                |       8 ++++++++
         A buffer.c                            |     209 +++++++++++++++++++++++++++++++
         A draw.c                              |     176 +++++++++++++++++++++++++++++++
         A input.c                             |     237 +++++++++++++++++++++++++++++++
         A io-abduco                           |      15 +++++++++++++++
         A io-files                            |      58 ++++++++++++++++++++++++++++++
         A io-grep                             |      16 ++++++++++++++++
         A io-man                              |       9 +++++++++
         A io-mblaze                           |      16 ++++++++++++++++
         A io-run                              |     100 +++++++++++++++++++++++++++++++
         A io-setfont                          |      11 +++++++++++
         A iomenu.1                            |      77 +++++++++++++++++++++++++++++++
         A main.c                              |      91 +++++++++++++++++++++++++++++++
         A main.h                              |      95 ++++++++++++++++++++++++++++++
         A nohup.out                           |      92 +++++++++++++++++++++++++++++++
         A util.c                              |      73 +++++++++++++++++++++++++++++++
       
       19 files changed, 1416 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/LICENSE b/LICENSE
       @@ -0,0 +1,21 @@
       +MIT License
       +
       +Copyright (c) 2016 Josuah Demangeon⠠⠵
       +
       +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.
   DIR diff --git a/Makefile b/Makefile
       @@ -0,0 +1,22 @@
       +CFLAGS    = -std=c89 -pedantic -Wall -Wextra -g -static
       +SRC       = main.c buffer.c util.c draw.c input.c
       +OBJ       = ${SRC:.c=.o}
       +
       +MANPREFIX = $(PREFIX)
       +
       +all: clean iomenu
       +
       +.c.o:
       +        ${CC} -c ${CFLAGS} $<
       +
       +iomenu: ${OBJ}
       +        ${CC} -o $@ ${OBJ} ${LDFLAGS}
       +        rm -f *.o
       +
       +clean:
       +        rm -f iomenu ${OBJ}
       +
       +install: iomenu
       +        mkdir -p  $(PREFIX)/bin $(MANPREFIX)/man/man1
       +        cp *.1 $(MANPREFIX)/man/man1/
       +        cp iomenu io-* $(PREFIX)/bin/
   DIR diff --git a/README b/README
       @@ -0,0 +1,90 @@
       +iomenu - Filter lines from stdin with an interactive menu
       +
       + .  _  __   _  __
       + | (_) ||| (/_ | | |_|
       +
       +________________________________________________________________________________
       +
       +        iomenu is a terminal tool to interactively select lines from stdin, and
       +        print them out to the standard output.
       +
       +        You can use scripts made for dmenu [1], as iomenu mostly the same way.
       +
       +        Thanks to the authors of dmenu [1], sandy [2], vis-menu[3], pep[4], ...
       +        that taught me C by writing some.
       +
       +
       +Getting started
       +________________________________________________________________________________
       +
       +        You can install iomenu by running:
       +        
       +        """
       +        make install
       +        """
       +        
       +        You can optionnaly set a "PREFIX" variable to set the path to
       +        install to:
       +        
       +        """
       +        make PREFIX="$HOME/bin" install
       +        """
       +
       +        All you need to build it is a C compiler:  It is plain C89 source
       +        code without external dependencies.
       +
       +        All usage details are written in the man page, "iomenu.1".
       +
       +
       +Examples
       +________________________________________________________________________________
       +
       +
       +Open a bookmark from a list in a text file
       +
       +        """
       +        iomenu < bookmarks-urls.txt | xargs firefox
       +        """
       +
       +
       +Go to a subdirectory
       +
       +        """
       +        cd "$(find . -type d | iomenu)"
       +        """
       +
       +
       +Edit a file located in ~
       +
       +        """
       +        $EDITOR "$(find -type f | iomenu)"
       +        """
       +
       +
       +Play an audio file
       +
       +        """
       +        mplayer "$(find ~/Music | iomenu)"
       +        """
       +
       +
       +Select a background job to attach to
       +
       +        """
       +        fg "%$(jobs | iomenu | cut -c 2)"
       +        """
       +
       +
       +Filter "ps" output and print a process ID
       +
       +        """
       +        { printf '#'; ps ax; } | iomenu -s '#' | sed -r 's/ *([0-9]*).*/\1/'
       +        """
       +
       +
       +________________________________________________________________________________
       +
       +1        http://git.suckless.org/dmenu/tree/dmenu.c
       +2        http://git.suckless.org/sandy/tree/sandy.c
       +3        http://github.com/martanne/vis/blob/master/vis-menu.c
       +4        http://github.com/charles-l/pep/blob/master/pep.c
   DIR diff --git a/TODO b/TODO
       @@ -0,0 +1,8 @@
       +-        Check return values for every function that may fail.
       +
       +-        Add support for a default input string (when I will need it or if
       +        someone ask for it).
       +
       +-        Fix the input shifting the line count by 1.
       +
       +-        Case insensitive match.
   DIR diff --git a/buffer.c b/buffer.c
       @@ -0,0 +1,209 @@
       +#include <ctype.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +
       +#include "main.h"
       +
       +
       +/*
       + * Fill the buffer apropriately with the lines and headers.
       + */
       +Buffer *
       +fill_buffer(char *separator)
       +{
       +        /* fill buffer with string */
       +        char    s[LINE_SIZE];
       +        Buffer *buffer = malloc(sizeof(Buffer));
       +        FILE   *fp     = stdin;
       +        int     l;
       +
       +        if (!fp)
       +                die("Can not open file for reading.");
       +
       +        buffer->input[0] = '\0';
       +        buffer->total = buffer->matching = 1;
       +
       +        /* empty line in case no line come from stdin */
       +        buffer->first = buffer->current = malloc(sizeof(Line));
       +        buffer->first->content = buffer->first->comment = "";
       +        buffer->first->next = buffer->first->prev = NULL;
       +        buffer->last = NULL;
       +
       +        /* read the file into a doubly linked list of lines */
       +        for (l = 1; fgets(s, LINE_SIZE, fp); buffer->total++, l++) {
       +                buffer->last = add_line(buffer, l, s, separator, buffer->last);
       +
       +                l = buffer->last->header ? 0 : l;
       +        }
       +
       +        /* prevent initial current line to be a header */
       +        buffer->current = buffer->first;
       +        while (buffer->current->next && buffer->current->header)
       +                buffer->current = buffer->current->next;
       +
       +        return buffer;
       +}
       +
       +
       +/*
       + * Add a line to the end of the current buffer.
       + */
       +Line *
       +add_line(Buffer *buffer, int number, char *s, char *separator, Line *prev)
       +{
       +        /* allocate new line */
       +        buffer->last          = new_line(s, separator);
       +        buffer->last->number  = number;
       +        buffer->last->matches = 1;  /* matches by default */
       +        buffer->matching++;
       +
       +        /* interlink with previous line if exists */
       +        if (prev) {
       +                prev->next = buffer->last;
       +                buffer->last->prev = prev;
       +        } else {
       +                buffer->first = buffer->last;
       +        }
       +
       +        return buffer->last;
       +}
       +
       +
       +/*
       + * Parse the line content to determine if it is a header and identify the
       + * separator if any.
       + */
       +Line *
       +new_line(char *s, char *separator)
       +{
       +        Line *line = malloc(sizeof(Line));
       +        char *sep  = separator ? strstr(s, separator) : NULL;
       +        int   pos  = sep ? (int) (sep - s) : (int) strlen(s) - 1;
       +
       +        /* header is when separator is the first character of the line */
       +        line->header = (sep == s);
       +
       +        /* strip trailing newline */
       +        s[strlen(s) - 1] = '\0';
       +
       +        /* fill line->content */
       +        line->content = malloc((pos + 1) * sizeof(char));
       +        strncpy(line->content, s, pos);
       +
       +        /* fill line->comment */
       +        line->comment = malloc((strlen(s) - pos) * sizeof(char));
       +        if (sep) {
       +                strcpy(line->comment, s + pos + strlen(separator));
       +        }
       +
       +        /* strip trailing whitespaces from line->content */
       +        for (pos--; pos > 0 && isspace(line->content[pos]); pos--)
       +                line->content[pos] = '\0';
       +
       +        /* strip leading  whitespaces from line->comment */
       +        for (pos = 0; isspace(line->comment[pos]); pos++);
       +        line->comment += pos;
       +
       +        return line;
       +}
       +
       +
       +/*
       + * Free the buffer, also recursing the doubly linked list.
       + */
       +void
       +free_buffer(Buffer *buffer)
       +{
       +        Line *next = NULL;
       +
       +        while (buffer->first) {
       +                next = buffer->first->next;
       +
       +                free(buffer->first);
       +
       +                buffer->first = next;
       +        }
       +
       +        free(buffer);
       +}
       +
       +
       +/*
       + * Set the line->matching state according to the return value of match_line,
       + * and buffer->matching to number of matching candidates.
       + *
       + * The incremental parameter sets whether check already matching or
       + * non-matching lines only.  This is for performance concerns.
       + */
       +void
       +filter_lines(Buffer *buffer, int inc)
       +{
       +        Line    *line = buffer->first;
       +        char   **tokv = NULL;
       +        char    *s, buf[sizeof buffer->input];
       +        size_t   n = 0, tokc = 0;
       +
       +        /* tokenize input from space characters, this comes from dmenu */
       +        strcpy(buf, buffer->input);
       +        for (s = strtok(buf, " "); s; s = strtok(NULL, " ")) {
       +                if (++tokc > n && !(tokv = realloc(tokv, ++n * sizeof(*tokv))))
       +                        die("cannot realloc memory for tokv\n");
       +
       +                tokv[tokc - 1] = s;
       +        }
       +
       +        /* match lines */
       +        buffer->matching = 0;
       +        while (line) {
       +                if (buffer->input[0] && !strcmp(buffer->input, line->content)) {
       +                        line->matches = 1;
       +                        buffer->current = line;
       +                } else if ((inc && line->matches) || (!inc && !line->matches)) {
       +                        line->matches     = match_line(line, tokv, tokc);
       +                        buffer->matching += line->header ? 0 : line->matches;
       +                }
       +
       +                line = line->next;
       +        }
       +}
       +
       +
       +/*
       + * Return whecher the line matches every string from tokv.
       + */
       +int
       +match_line(Line *line, char **tokv, size_t tokc)
       +{
       +        size_t i, match = 1, offset = 0;
       +
       +        if (line->header)
       +                return 1;
       +
       +        for (i = 0; i < tokc && match; i++)
       +                match = !!strstr(line->content + offset, tokv[i]);
       +
       +        return match;
       +}
       +
       +
       +/*
       + * Seek the previous matching line, or NULL if none matches.
       + */
       +Line *
       +matching_prev(Line *line)
       +{
       +        while ((line = line->prev) && (!line->matches || line->header));
       +        return line;
       +}
       +
       +
       +/*
       + * Seek the next matching line, or NULL if none matches.
       + */
       +Line *
       +matching_next(Line *line)
       +{
       +        while ((line = line->next) && (!line->matches || line->header));
       +        return line;
       +}
   DIR diff --git a/draw.c b/draw.c
       @@ -0,0 +1,176 @@
       +#include <stdlib.h>
       +#include <string.h>
       +#include <stdio.h>
       +#include <sys/ioctl.h>
       +
       +#include "main.h"
       +
       +
       +/*
       + * Print a line to stderr.
       + */
       +void
       +draw_line(Line *line, int current, const int cols, Opt *opt)
       +{
       +        char *content = expand_tabs(line->content);
       +        char *comment = expand_tabs(line->comment);
       +        char  output[LINE_SIZE * sizeof(char)] = "\033[K";
       +        int n = 0;
       +
       +        if (opt->line_numbers && !line->header) {
       +                strcat(output, current ? "\033[1;37m" : "\033[1;30m");
       +                sprintf(output + strlen(output), "%7d\033[m ", line->number);
       +        } else {
       +                strcat(output, current ? "\033[1;31m      > " : "        ");
       +        }
       +        n += 8;
       +
       +
       +        /* highlight current line */
       +        if (current)
       +                strcat(output, "\033[1;33m");
       +
       +        /* content */
       +        strncat(output, content, cols - n);
       +        n += strlen(content);
       +
       +        /* align comment */
       +        if (!line->header && line->comment[0] != '\0') {
       +                /* MAX with '1' as \033[0C still move 1 to the right */
       +                sprintf(output + strlen(output), "\033[%dC",
       +                        MAX(1, 40 - n));
       +                n += MAX(1, 40 - n);
       +        } else if (line->header)
       +
       +        /* comment */
       +        strcat(output, "\033[1;30m");
       +        strncat(output, comment, cols - n);
       +        n += strlen(comment);
       +
       +        strcat(output, "\033[m\n");
       +
       +        fputs(output, stderr);
       +
       +        free(content);
       +        free(comment);
       +}
       +
       +
       +/*
       + * Print all the lines from an array of pointer to lines.
       + *
       + * The total number oflines printed shall not excess 'count'.
       + */
       +void
       +draw_lines(Buffer *buffer, int count, int cols, Opt *opt)
       +{
       +        Line *line = buffer->current;
       +        int i = 0;
       +        int j = 0;
       +
       +        /* seek back from current line to the first line to print */
       +        while (line && i < count - OFFSET) {
       +                i    = line->matches ? i + 1 : i;
       +                line = line->prev;
       +        }
       +        line = line ? line : buffer->first;
       +
       +        /* print up to count lines that match the input */
       +        while (line && j < count) {
       +                if (line->matches) {
       +                        draw_line(line, line == buffer->current, cols, opt);
       +                        j++;
       +                }
       +
       +                line = line->next;
       +        }
       +
       +        /* continue up to the end of the screen clearing it */
       +        for (; j < count; j++)
       +                fputs("\r\033[K\n", stderr);
       +}
       +
       +
       +/*
       + * Update the screen interface and print all candidates.
       + *
       + * This also has to clear the previous lines.
       + */
       +void
       +draw_screen(Buffer *buffer, int tty_fd, Opt *opt)
       +{
       +        struct winsize w;
       +        int count;
       +
       +        if (ioctl(tty_fd, TIOCGWINSZ, &w) < 0)
       +                die("could not get terminal size");
       +
       +        count = MIN(opt->lines, w.ws_row - 2);
       +
       +        fputs("\n", stderr);
       +        draw_lines(buffer, count, w.ws_col, opt);
       +
       +        /* go up to the prompt position and update it */
       +        fprintf(stderr, "\033[%dA", count + 1);
       +        draw_prompt(buffer, w.ws_col, opt);
       +}
       +
       +
       +void
       +draw_clear(int lines)
       +{
       +        int i;
       +
       +        for (i = 0; i < lines + 1; i++)
       +                fputs("\r\033[K\n", stderr);
       +        fprintf(stderr, "\033[%dA", lines + 1);
       +}
       +
       +
       +/*
       + * Print the prompt, before the input, with the number of candidates that
       + * match.
       + */
       +void
       +draw_prompt(Buffer *buffer, int cols, Opt *opt)
       +{
       +        size_t  i;
       +        int     matching = buffer->matching;
       +        int     total    = buffer->total;
       +        char   *input    = expand_tabs(buffer->input);
       +        char   *suggest  = expand_tabs(buffer->current->content);
       +
       +        /* for the '/' separator between the numbers */
       +        cols--;
       +
       +        /* number of digits */
       +        for (i = matching; i; i /= 10, cols--);
       +        for (i = total;    i; i /= 10, cols--);
       +        cols -= !matching ? 1 : 0;  /* 0 also has one digit*/
       +
       +        /* actual prompt */
       +        fprintf(stderr, "\r%-6s\033[K\033[1m>\033[m ", opt->prompt);
       +        cols -= 2 + MAX(strlen(opt->prompt), 6);
       +
       +        /* input without overflowing terminal width */
       +        for (i = 0; i < strlen(input) && cols > 0; cols--, i++)
       +                fputc(input[i], stderr);
       +
       +        /* save the cursor position at the end of the input */
       +        fputs("\033[s", stderr);
       +
       +        /* grey */
       +        fputs("\033[1;30m", stderr);
       +
       +        /* go to the end of the line */
       +        fprintf(stderr, "\033[%dC", cols);
       +
       +        /* total match and line count at the end of the line */
       +        fprintf(stderr, "%d/%d", matching, total);
       +
       +        /* restore cursor position at the end of the input */
       +        fputs("\033[m\033[u", stderr);
       +
       +        free(input);
       +        free(suggest);
       +}
   DIR diff --git a/input.c b/input.c
       @@ -0,0 +1,237 @@
       +#include <ctype.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <termios.h>
       +
       +#include "main.h"
       +
       +
       +/*
       + * Listen for the user input and call the appropriate functions.
       + */
       +int
       +input_get(Buffer *buffer, int tty_fd, Opt *opt)
       +{
       +        FILE *tty_fp = fopen("/dev/tty", "r");
       +        int   exit_code;
       +
       +        /* receive one character at a time from the terminal */
       +        struct termios termio_old = set_terminal(tty_fd);
       +
       +        /* get input char by char from the keyboard. */
       +        while ((exit_code = input_key(tty_fp, buffer, opt)) == CONTINUE)
       +                draw_screen(buffer, tty_fd, opt);
       +
       +        /* resets the terminal to the previous state. */
       +        tcsetattr(tty_fd, TCSANOW, &termio_old);
       +
       +        fclose(tty_fp);
       +
       +        return exit_code;
       +}
       +
       +
       +/*
       + * Perform action associated with key
       + */
       +int
       +input_key(FILE *tty_fp, Buffer *buffer, Opt *opt)
       +{
       +        char key = fgetc(tty_fp);
       +
       +        if (key == opt->validate_key) {
       +                action_print_selection(buffer, 0, opt);
       +                return EXIT_SUCCESS;
       +        }
       +
       +        switch (key) {
       +
       +        case CONTROL('C'):
       +                draw_clear(opt->lines);
       +                return EXIT_FAILURE;
       +
       +        case CONTROL('U'):
       +                buffer->input[0] = '\0';
       +                buffer->current  = buffer->first;
       +                filter_lines(buffer, 0);
       +                action_jump(buffer, 1);
       +                action_jump(buffer, -1);
       +                break;
       +
       +        case CONTROL('W'):
       +                action_remove_word_input(buffer);
       +                filter_lines(buffer, 0);
       +                break;
       +
       +        case 127:
       +        case CONTROL('H'):  /* backspace */
       +                buffer->input[strlen(buffer->input) - 1] = '\0';
       +                filter_lines(buffer, 0);
       +                action_jump(buffer, 0);
       +                break;
       +
       +        case CONTROL('N'):
       +                action_jump(buffer, 1);
       +                break;
       +
       +        case CONTROL('P'):
       +                action_jump(buffer, -1);
       +                break;
       +
       +        case CONTROL('I'):  /* tab */
       +                strcpy(buffer->input, buffer->current->content);
       +                filter_lines(buffer, 1);
       +                break;
       +
       +        case CONTROL('J'):
       +        case CONTROL('M'):  /* enter */
       +                action_print_selection(buffer, 0, opt);
       +                return EXIT_SUCCESS;
       +
       +        case CONTROL('@'):  /* ctrl + space */
       +                action_print_selection(buffer, 1, opt);
       +                return EXIT_SUCCESS;
       +
       +        case CONTROL('['):  /* escape */
       +                switch (fgetc(tty_fp)) {
       +
       +                case 'O':  /* arrow keys */
       +                        switch (fgetc(tty_fp)) {
       +
       +                        case 'A':  /* up */
       +                                action_jump(buffer, -1);
       +                                break;
       +
       +                        case 'B':  /* Down */
       +                                action_jump(buffer, 1);
       +                                break;
       +                        }
       +                        break;
       +
       +                case '[':  /* page control */
       +                        key = fgetc(tty_fp);
       +                        switch(fgetc(tty_fp)) {
       +
       +                        case '~':
       +                                switch (key) {
       +
       +                                case '5': /* page up */
       +                                        action_jump(buffer, -10);
       +                                        break;
       +
       +                                case '6': /* page down */
       +                                        action_jump(buffer, 10);
       +                                        break;
       +                                }
       +                                break;
       +                        }
       +                        break;
       +                }
       +                break;
       +
       +        default:
       +                action_add_character(buffer, key);
       +        }
       +
       +        return CONTINUE;
       +}
       +
       +
       +/*
       + * Set the current line to next/previous/any matching line.
       + */
       +void
       +action_jump(Buffer *buffer, int direction)
       +{
       +        Line * line   = buffer->current;
       +        Line * result = line;
       +
       +        if (direction == 0 && !buffer->current->matches) {
       +                line   =               matching_next(buffer->current);
       +                line   = line ? line : matching_prev(buffer->current);
       +                result = line ? line : result;
       +        }
       +
       +        for (; direction < 0 && line; direction++) {
       +                line   = matching_prev(line);
       +                result = line ? line : result;
       +        }
       +
       +        for (; direction > 0 && line; direction--) {
       +                line   = matching_next(line);
       +                result = line ? line : result;
       +        }
       +
       +        buffer->current = result;
       +}
       +
       +
       +/*
       + * Remove the last word from the buffer's input
       + */
       +void
       +action_remove_word_input(Buffer *buffer)
       +{
       +        size_t length = strlen(buffer->input) - 1;
       +        int i;
       +
       +        for (i = length; i >= 0 && isspace(buffer->input[i]); i--)
       +                buffer->input[i] = '\0';
       +
       +        length = strlen(buffer->input) - 1;
       +        for (i = length; i >= 0 && !isspace(buffer->input[i]); i--)
       +                buffer->input[i] = '\0';
       +}
       +
       +
       +/*
       + * Add a character to the buffer input and filter lines again.
       + */
       +void
       +action_add_character(Buffer *buffer, char key)
       +{
       +        size_t length = strlen(buffer->input);
       +
       +        if (isprint(key)) {
       +                buffer->input[length]     = key;
       +                buffer->input[length + 1] = '\0';
       +        }
       +
       +        filter_lines(buffer, 1);
       +
       +        action_jump(buffer, 0);
       +}
       +
       +
       +/*
       + * Send the selection to stdout.
       + */
       +void
       +action_print_selection(Buffer *buffer, int return_input, Opt *opt)
       +{
       +        Line *line = NULL;
       +
       +        fputs("\r\033[K", stderr);
       +
       +        if (opt->print_header) {
       +                for (line = buffer->current; line; line = line->prev) {
       +                        if (line->header) {
       +                                fputs(line->comment, stdout);
       +                                break;
       +                        }
       +                }
       +                fputc((int) '\t', stdout);
       +        }
       +
       +        if (opt->print_number) {
       +                if (buffer->matching > 0)
       +                        printf("%d\n", buffer->current->number);
       +
       +        } else if (return_input || !buffer->matching) {
       +                puts(buffer->input);
       +
       +        } else if (buffer->matching > 0) {
       +                puts(buffer->current->content);
       +        }
       +}
   DIR diff --git a/io-abduco b/io-abduco
       @@ -0,0 +1,15 @@
       +# Prompt for an abduco session to attach to
       +
       +if [ "$ABDUCO" ]
       +then
       +        printf 'session already active: %s\n' "$ABDUCO"
       +        exit 1
       +fi
       +
       +name="$(printf '#%s' "$(
       +        abduco | sed -r 's/(.*)\t(.*)/\2 # \1/'
       +)" | iomenu -s '#')"
       +
       +[ "$SSH_CLIENT$SSH_TTY$SSH_CONNECTION" ] && e='^\' || e='^Z'
       +
       +TERM=screen ABDUCO="$name" exec abduco -e "$e" -A "$name" "$SHELL"
   DIR diff --git a/io-files b/io-files
       @@ -0,0 +1,58 @@
       +# Prompt a file to open in PAGER, with an history.  In less(1), 'v' to edit.
       +
       +
       +CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
       +
       +
       +path()
       +(
       +        if [ "$1" ]
       +        then
       +                printf '%s\n' "$(cd "${1%/*}"; pwd)/${1##*/}"
       +        else
       +                {
       +                        printf '#\n# Recent files\n'
       +                        [ -f "$CACHE/iomenu/files" ] &&
       +                        tac "$CACHE/iomenu/files"
       +
       +                        printf '#\n# Current directory\n'
       +                        find "$PWD" -maxdepth 1 -type f
       +
       +                        printf '#\n# All files\n'
       +                        find ~ -type f ! -path '*/.cache/*' ! -path '*/.git/*'
       +
       +                } | sed "s|$HOME|~|" | iomenu -l 256 -s '#' | sed "s|~|$HOME|"
       +
       +        fi | tee -a "$CACHE/iomenu/files"
       +)
       +
       +
       +history()
       +(
       +        sort "$CACHE/iomenu/files" | uniq -d | while IFS='' read -r f
       +        do
       +                printf '%s\n' "$(
       +                        grep -Fxv "$f" "$CACHE/iomenu/files"
       +                )" "$f" > "$CACHE/iomenu/files"
       +        done
       +
       +        printf '%s\n' "$(tail "$CACHE/iomenu/files")" > "$CACHE/iomenu/files"
       +)
       +
       +
       +main()
       +(
       +        mkdir -p "$CACHE/iomenu"
       +
       +        file="$(path "$1")"
       +
       +        # terminal name
       +        printf '\033]0;%s\007' "$(printf %s "$file" | sed "s|$HOME|~|")"
       +
       +        history
       +
       +        [ "$file" ] && [ -d "${file%/*}" ] && exec $EDITOR "$file"
       +)
       +
       +
       +main "$@"
   DIR diff --git a/io-grep b/io-grep
       @@ -0,0 +1,16 @@
       +directory="$(
       +        cd "$HOME"
       +        find . -type d ! -path '*/.git/*' ! -name '.git' |
       +                sed 's/^./~/' | iomenu -l 256
       +)"
       +
       +directory="$HOME${directory#\~}"
       +
       +grep -rL '\x00' "$directory" | while IFS='' read -r path
       +do
       +        printf '#io-grep %s\n' "~${path#$HOME}"
       +         cat "$path"
       +done | iomenu -s '#io-grep' -H -N -l 256 | {
       +        IFS='        ' read -r path line
       +        exec $EDITOR +"$line"g "$HOME${path#\~}"
       +}
   DIR diff --git a/io-man b/io-man
       @@ -0,0 +1,9 @@
       +# prompt a man page to open
       +
       +man "$(
       +        IFS=':'
       +        find $(manpath -q) ! -type d |
       +                sed -r 's/.*\/(.*).[0-9](.gz)?$/\1/' |
       +                sort -u |
       +                iomenu
       +)"
   DIR diff --git a/io-mblaze b/io-mblaze
       @@ -0,0 +1,16 @@
       +T='        '
       +choice="$(
       +        mdirs "${MAIL%/*}" | while IFS='' read -r dir
       +        do
       +                printf '#\n# %s\n' "${dir##*/}"
       +
       +                mlist "$dir" | mpick :u | msort -d | mthread |
       +                mscan -f '%D  %24f %u%t%2i%120S'
       +
       +        done | iomenu -N -H -s '#' -l 255
       +)"
       +
       +[ "$choice" ] || exit 0
       +
       +mlist "${MAIL%/*}/${choice%%$T*}" | mpick :u | msort -d | mthread |
       +sed -n "${choice#*$T}p" | mshow | $PAGER
   DIR diff --git a/io-run b/io-run
       @@ -0,0 +1,100 @@
       +# Prompt for a programs to run
       +
       +
       +CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
       +
       +
       +usage()
       +{
       +        printf 'Usage: %s [cmd [args...] [+]]
       +        
       +cmd        do not prompt for a command and run cmd right away
       +args        do not prompt for arguments neither and use arg
       ++        if present after the arguments, prompt for a path\n' "${0##*/}"
       +}
       +
       +
       +#
       +# Update the cache and get the command to run.
       +#
       +update_cache()
       +(
       +        IFS=':' u=0
       +
       +        for dir in $PATH
       +        do
       +                [ "$CACHE/dmenu_run" -ot "$dir" ] && u=1
       +        done
       +
       +        [ "$u" -eq 1 ] && find -L $PATH -type f -exec test -x {} \; -print |
       +                sed 's|.*/||' | sort -u > "$CACHE/dmenu_run"
       +)
       +
       +
       +#
       +# Prompt for options for a given command and log it to an history file
       +#
       +get_options()
       +(
       +        local command="$1"
       +
       +        printf '%s ' "$command" >> "$CACHE/iomenu/run"
       +
       +        while read -r cmd opt
       +        do
       +                [ "$command" = "$cmd" ] && printf '%s\n' "$opt"
       +        done < "$CACHE/iomenu/run" |
       +                iomenu -p "$command" | tee -a "$CACHE/iomenu/run"
       +
       +        sort -u "$CACHE/iomenu/run" -o "$CACHE/iomenu/run"
       +)
       +
       +
       +#
       +# Prompt for a file path in $HOME and print it.
       +#
       +get_path()
       +(
       +        find "$HOME" ! -path "$CACHE" ! -path '*/.git/*' |
       +                sed -r "s/.{${#HOME}}/~/" | iomenu -l 256 | sed 's/^~//'
       +)
       +
       +
       +#
       +# Get the options according to the command and run it
       +#
       +run()
       +(
       +        command="${1:-$(iomenu -l 256 -s '#' < "$CACHE/dmenu_run")}"
       +
       +        [ -z "$command" ] && exit 1
       +
       +        options="$(get_options "$command")"
       +
       +        if [ "$options" ] && [ -z "${options%%*+}" ]
       +        then
       +                path="$(get_path)" options="${options%+}"
       +        fi
       +
       +        if [ "$path" ]
       +        then exec $command $options "$path"
       +        else exec $command $options
       +        fi
       +)
       +
       +
       +main()
       +(
       +        mkdir -p "$CACHE/iomenu"
       +
       +        if [ $# -gt 0 ] && [ -z "${1##-*}" ]
       +        then
       +                usage
       +        else
       +                update_cache
       +                run "$@"
       +        fi
       +)
       +
       +
       +main "$@"
   DIR diff --git a/io-setfont b/io-setfont
       @@ -0,0 +1,11 @@
       +setfont "$(
       +        find /usr/share ~ -type d -name consolefonts | while IFS='' read -r path
       +        do
       +                cd "$path" || exit 1
       +
       +                fonts="$(find . -type f | cut -c 3-)"
       +
       +                [ "$fonts" ] && printf '#\n# %s\n%s\n' "$path" "$fonts"
       +
       +        done | iomenu -l 256 -s '#' -H | sed 's/\t/\//'
       +)"
   DIR diff --git a/iomenu.1 b/iomenu.1
       @@ -0,0 +1,77 @@
       +.Dd $Mdocdate: October 16 2016 $
       +.Dt IOMENU 1
       +.Os
       +.Sh NAME
       +.Nm iomenu
       +.Op Fl nNHksl
       +.
       +.
       +.Sh DESCRIPTION
       +.
       +The
       +.Nm
       +utility filters lines form stdin interactively with the keyboard, and print
       +the selected line to stdout.
       +.Pp
       +Lower case switches are for the interface, uppercase switches are for
       +input/output.
       +.Bl -tag
       +.It Fl n
       +Display line numbers in interface.
       +.
       +.It Fl N
       +Return the line number rather than the match.
       +.
       +.It Fl H
       +Return the current header that the selection belongs to in addition to the
       +match, delimited by a tab.
       +.
       +.It Fl k Cm key
       +Key to use to validate current selection in addition to Enter.
       +.
       +.It Fl s Cm separator
       +Character separating the content from the comments.  Every character after
       +the separator will be considered as comment and will be grayed and aligned
       +in the interface.
       +.Pp
       +If a separator is at the beginning of a line (without leading space), the
       +line is considered as a section header, and it will always be displayed
       +regardless if it matches or not.
       +.
       +.It Fl l Cm lines
       +Number of lines to display at once.  Default is 30.
       +.El
       +.
       +.
       +.Sh KEYBINDINGS
       +.
       +.Bl -tag
       +.It Cm ^M, ^J, Enter
       +Print the matched line to stdout and exit.
       +.
       +.It Cm ^@, ^Space
       +Print the content of the input rather than the matched line to stdout and exit.
       +.
       +.It Cm ^P / ^N, Up / Down
       +Navigate to the previous / next line.
       +.
       +.It Cm PageUp / PageDown
       +Navigate 10 lines up / down.
       +.
       +.It Cm ^I, Tab
       +Set input to the currently highlighted candidate, then cycle through candidate
       +list.
       +.
       +.It Cm ^H, Backspace
       +Delete one char backward, but if there is no char
       +backward, it should return an error code of 1.
       +.
       +.It Cm ^C
       +Cancel, and make filter return the error code of 1.
       +.
       +.It Cm ^W
       +Deletes the last entered word.
       +.
       +.It Cm ^U
       +Deletes the entire input and jump to the first line.
       +.El
   DIR diff --git a/main.c b/main.c
       @@ -0,0 +1,91 @@
       +#include <ctype.h>
       +#include <fcntl.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <sys/ioctl.h>
       +#include <termios.h>
       +#include <unistd.h>
       +
       +#include "main.h"
       +
       +
       +void
       +usage(void)
       +{
       +        fputs("usage: iomenu [-n] [-N] [-k key] [-s separator] ", stderr);
       +        fputs("[-p prompt] [-l lines]\n", stderr);
       +
       +        exit(EXIT_FAILURE);
       +}
       +
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        int i, exit_code, tty_fd = open("/dev/tty", O_RDWR);
       +        Buffer *buffer = NULL;
       +        Opt    *opt    = malloc(sizeof(Opt));
       +
       +        opt->line_numbers  = 0;
       +        opt->print_number  = 0;
       +        opt->validate_key  = CONTROL('M');
       +        opt->separator     = NULL;
       +        opt->lines         = 30;
       +        opt->prompt        = "";
       +
       +        /* command line arguments */
       +        for (i = 1; i < argc; i++) {
       +                if (argv[i][0] != '-' || strlen(argv[i]) != 2)
       +                        usage();
       +
       +                switch (argv[i][1]) {
       +                case 'n':
       +                        opt->line_numbers = 1;
       +                        break;
       +                case 'N':
       +                        opt->print_number = 1;
       +                        opt->line_numbers = 1;
       +                        break;
       +                case 'H':
       +                        opt->print_header = 1;
       +                        break;
       +                case 'k':
       +                        opt->validate_key = (argv[++i][0] == '^') ?
       +                                CONTROL(toupper(argv[i][1])): argv[i][0];
       +                        break;
       +                case 's':
       +                        opt->separator = argv[++i];
       +                        break;
       +                case 'l':
       +                        if (sscanf(argv[++i], "%d", &opt->lines) <= 0)
       +                                die("wrong number format after -l");
       +                        break;
       +                case 'p':
       +                        if (++i >= argc)
       +                                die("wrong string format after -p");
       +                        opt->prompt = argv[i];
       +                        break;
       +                default:
       +                        usage();
       +                }
       +        }
       +
       +        /* command line arguments */
       +        buffer = fill_buffer(opt->separator);
       +
       +        /* set the interface */
       +        draw_screen(buffer, tty_fd, opt);
       +
       +        /* listen and interact to input */
       +        exit_code = input_get(buffer, tty_fd, opt);
       +
       +        draw_clear(opt->lines);
       +
       +        /* close files descriptors and pointers, and free memory */
       +        close(tty_fd);
       +        free(opt);
       +        free_buffer(buffer);
       +
       +        return exit_code;
       +}
   DIR diff --git a/main.h b/main.h
       @@ -0,0 +1,95 @@
       +#define LINE_SIZE  1024
       +#define OFFSET     5
       +#define CONTINUE   2  /* as opposed to EXIT_SUCCESS and EXIT_FAILURE */
       +
       +#define CONTROL(char) (char ^ 0x40)
       +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
       +#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
       +
       +
       +/*
       + * Options from the command line, to pass to each function that need some
       + */
       +typedef struct Opt {
       +        int   line_numbers;
       +        int   print_number;
       +        int   print_header;
       +        char  validate_key;
       +        char *separator;
       +        int   lines;
       +        char *prompt;
       +} Opt;
       +
       +
       +/*
       + * Line coming from stdin, wrapped in a header.
       + */
       +typedef struct Line {
       +        char *content;               /* sent as output and matched by input */
       +        char *comment;               /* displayed at the right of the content */
       +
       +        int  number;                 /* set here as order will not change */
       +        int  matches;                /* whether it matches buffer's input */
       +        int  header;                 /* whether the line is a header */
       +
       +        struct Line *prev;           /* doubly linked list structure */
       +        struct Line *next;
       +} Line;
       +
       +
       +/*
       + * Buffer containing a doubly linked list of headers
       + */
       +typedef struct Buffer {
       +        int total;                   /* total number of line in buffer */
       +        int matching;                /* number lines matching the input */
       +
       +        char    input[LINE_SIZE];    /* string from user's keyboard */
       +
       +        Line   *current;             /* selected line, highlighted */
       +        Line   *first;               /* boundaries of the linked list */
       +        Line   *last;
       +} Buffer;
       +
       +
       +/* main */
       +
       +void     usage(void);
       +
       +
       +/* buffer */
       +
       +Buffer * fill_buffer(char *);
       +void     free_buffer(Buffer *);
       +Line   * add_line(Buffer *, int, char *, char *, Line *);
       +Line   * new_line(char *, char *);
       +Line   * matching_next(Line *);
       +Line   * matching_prev(Line *);
       +int      match_line(Line *, char **, size_t);
       +void     filter_lines(Buffer *, int);
       +
       +
       +/* draw */
       +
       +void     draw_screen(Buffer *, int, Opt *);
       +void     draw_clear(int);
       +void     draw_line(Line *, int, int, Opt *);
       +void     draw_lines(Buffer *, int, int, Opt *);
       +void     draw_prompt(Buffer *, int, Opt *);
       +
       +
       +/* input */
       +
       +int      input_get(Buffer *, int, Opt *);
       +int      input_key(FILE *, Buffer *, Opt *);
       +void     action_jump(Buffer *, int);
       +void     action_print_selection(Buffer *,int, Opt *);
       +void     action_remove_word_input(Buffer *);
       +void     action_add_character(Buffer *, char);
       +
       +
       +/* util */
       +
       +void             die(const char *);
       +struct termios   set_terminal(int);
       +char           * expand_tabs(char *);
   DIR diff --git a/nohup.out b/nohup.out
       @@ -0,0 +1,92 @@
       +build: Installing tmux
       +checking for a BSD-compatible install... /usr/bin/install -c
       +checking whether build environment is sane... yes
       +checking for a thread-safe mkdir -p... /bin/mkdir -p
       +checking for gawk... no
       +checking for mawk... mawk
       +checking whether make sets $(MAKE)... yes
       +checking whether make supports nested variables... yes
       +checking build system type... x86_64-unknown-linux-gnu
       +checking host system type... x86_64-unknown-linux-gnu
       +checking for gcc... gcc
       +checking whether the C compiler works... yes
       +checking for C compiler default output file name... a.out
       +checking for suffix of executables... 
       +checking whether we are cross compiling... no
       +checking for suffix of object files... o
       +checking whether we are using the GNU C compiler... yes
       +checking whether gcc accepts -g... yes
       +checking for gcc option to accept ISO C89... none needed
       +checking whether gcc understands -c and -o together... yes
       +checking for style of include used by make... GNU
       +checking dependency style of gcc... gcc3
       +checking how to run the C preprocessor... gcc -E
       +checking for grep that handles long lines and -e... /bin/grep
       +checking for egrep... /bin/grep -E
       +checking for pkg-config... /usr/bin/pkg-config
       +checking pkg-config is at least version 0.9.0... yes
       +checking for glibc... yes
       +checking for ANSI C header files... yes
       +checking for sys/types.h... yes
       +checking for sys/stat.h... yes
       +checking for stdlib.h... yes
       +checking for string.h... yes
       +checking for memory.h... yes
       +checking for strings.h... yes
       +checking for inttypes.h... yes
       +checking for stdint.h... yes
       +checking for unistd.h... yes
       +checking bitstring.h usability... no
       +checking bitstring.h presence... no
       +checking for bitstring.h... no
       +checking dirent.h usability... yes
       +checking dirent.h presence... yes
       +checking for dirent.h... yes
       +checking fcntl.h usability... yes
       +checking fcntl.h presence... yes
       +checking for fcntl.h... yes
       +checking for inttypes.h... (cached) yes
       +checking libutil.h usability... no
       +checking libutil.h presence... no
       +checking for libutil.h... no
       +checking ndir.h usability... no
       +checking ndir.h presence... no
       +checking for ndir.h... no
       +checking paths.h usability... yes
       +checking paths.h presence... yes
       +checking for paths.h... yes
       +checking pty.h usability... yes
       +checking pty.h presence... yes
       +checking for pty.h... yes
       +checking for stdint.h... (cached) yes
       +checking sys/dir.h usability... yes
       +checking sys/dir.h presence... yes
       +checking for sys/dir.h... yes
       +checking sys/ndir.h usability... no
       +checking sys/ndir.h presence... no
       +checking for sys/ndir.h... no
       +checking sys/tree.h usability... no
       +checking sys/tree.h presence... no
       +checking for sys/tree.h... no
       +checking term.h usability... no
       +checking term.h presence... no
       +checking for term.h... no
       +checking util.h usability... no
       +checking util.h presence... no
       +checking for util.h... no
       +checking for library containing flock... none required
       +checking for dirfd... yes
       +checking for flock... yes
       +checking for prctl... yes
       +checking for sysconf... yes
       +checking for cfmakeraw... yes
       +checking for library containing clock_gettime... none required
       +checking for LIBEVENT... no
       +checking for library containing event_init... no
       +checking event.h usability... no
       +checking event.h presence... no
       +checking for event.h... no
       +configure: error: "libevent not found"
       +make: *** No rule to make target 'install'.  Stop.
       +build: Updating index in /home/josuah/.local/tmux
       +build: Removing broken links from /home/josuah/.local
   DIR diff --git a/util.c b/util.c
       @@ -0,0 +1,73 @@
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <termios.h>
       +#include <unistd.h>
       +
       +#include "main.h"
       +
       +
       +/*
       + * Reset the terminal state and exit with error.
       + */
       +void
       +die(const char *s)
       +{
       +        /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
       +        fprintf(stderr, "%s\n", s);
       +        exit(EXIT_FAILURE);
       +}
       +
       +
       +/*
       + * Set terminal to send one char at a time for interactive mode, and return the
       + * last terminal state.
       + */
       +struct termios
       +set_terminal(int tty_fd)
       +{
       +        struct termios termio_old;
       +        struct termios termio_new;
       +
       +        /* set the terminal to send one key at a time. */
       +
       +        /* get the terminal's state */
       +        if (tcgetattr(tty_fd, &termio_old) < 0)
       +                die("Can not get terminal attributes with tcgetattr().");
       +
       +        /* create a new modified state by switching the binary flags */
       +        termio_new          = termio_old;
       +        termio_new.c_lflag &= ~(ICANON | ECHO | IGNBRK);
       +
       +        /* apply this state to current terminal now (TCSANOW) */
       +        tcsetattr(tty_fd, TCSANOW, &termio_new);
       +
       +        return termio_old;
       +}
       +
       +
       +/*
       + * Replace tab as a multiple of 8 spaces in a line.
       + *
       + * Allocates memory.
       + */
       +char *
       +expand_tabs(char *line)
       +{
       +        size_t i, n;
       +        char *converted = malloc(sizeof(char) * (strlen(line) * 8 + 1));
       +
       +        for (i = 0, n = 0; i < strlen(line); i++, n++) {
       +                if (line[i] == '\t') {
       +                        for (; n == 0 || n % 8 != 0; n++)
       +                                converted[n] = ' ';
       +                        n--;
       +                } else {
       +                        converted[n] = line[i];
       +                }
       +        }
       +
       +        converted[n] = '\0';
       +
       +        return converted;
       +}