on my way for cleaning this up - 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 13035ab96ecce34b052d703bebe4bc995ecc3920
 (DIR) parent 35d50bc1a88b26d1f57219a9f3f7b1a907c57498
 (HTM) Author: Josuah Demangeon⠠⠵ <mail@josuah.net>
       Date:   Thu, 16 Mar 2017 22:26:25 +0100
       
       on my way for cleaning this up
       
       Diffstat:
         A .gitignore                          |       1 +
         M LICENSE                             |      30 ++++++++++++------------------
         M Makefile                            |       2 +-
         M iomenu.c                            |     479 ++++++++++---------------------
       
       4 files changed, 159 insertions(+), 353 deletions(-)
       ---
 (DIR) diff --git a/.gitignore b/.gitignore
       @@ -0,0 +1 @@
       +iomenu
 (DIR) diff --git a/LICENSE b/LICENSE
       @@ -1,21 +1,15 @@
       -MIT License
       +ISC Licence
        
       -Copyright (c) 2016 Josuah Demangeon⠠⠵
       +Copyright (c) 2017 by 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:
       +Permission to use, copy, modify, and/or distribute this software for any
       +purpose with or without fee is hereby granted, provided that the above
       +copyright notice and this permission notice appear in all copies.
        
       -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.
       +THE SOFTWARE IS PROVIDED “AS IS” AND ISC DISCLAIMS ALL WARRANTIES
       +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       +MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY
       +SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 (DIR) diff --git a/Makefile b/Makefile
       @@ -1,4 +1,4 @@
       -CFLAGS    = -std=c99 -pedantic -Wall -Wextra -g -static
       +CFLAGS    = -std=c99 -Wpedantic -Wall -Wextra -g -static
        OBJ       = ${SRC:.c=.o}
        
        all: clean iomenu
 (DIR) diff --git a/iomenu.c b/iomenu.c
       @@ -9,64 +9,83 @@
        #include <sys/ioctl.h>
        
        
       -/*--- constants --------------------------------------------------------------*/
       -
       -#define LINE_SIZE  1024
        #define OFFSET     5
        #define CONTINUE   2  /* as opposed to EXIT_SUCCESS and EXIT_FAILURE */
        
       -
       -/*--- macros -----------------------------------------------------------------*/
       -
        #define CONTROL(char) (char ^ 0x40)
        #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
        #define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
        
        
       -/*--- structures -------------------------------------------------------------*/
       -
        typedef struct Line {
                char *text;  /* sent as output and matched by input */
                int   match;   /* whether it matches buffer's input */
        } Line;
        
        
       -/*--- variables --------------------------------------------------------------*/
       -
        Line **buffer;
       -int    current, matching, total;
       -int    opt_lines;
       -char  *opt_prompt, *input;
       +size_t current = 0, matching = 0, total = 0;
       +char  *input = "";
       +int    opt_lines         = 30;
       +char  *opt_prompt        = "";
       +
        
       +void
       +die(const char *s)
       +{
       +        /* tcsetattr(STDIN_FILENO, TCSANOW, &termio_old); */
       +        fprintf(stderr, "%s\n", s);
       +        exit(EXIT_FAILURE);
       +}
       +
       +
       +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 buffer[current] terminal now (TCSANOW) */
       +        tcsetattr(tty_fd, TCSANOW, &termio_new);
       +
       +        return termio_old;
       +}
        
       -/*--- functions --------------------------------------------------------------*/
        
       -/*
       - * Fill the buffer apropriately with the lines
       - */
        void
        fill_buffer(void)
        {
                extern Line **buffer;
        
       -        char  s[LINE_SIZE];
       -        size_t size = 1;
       +        char  s[BUFSIZ];
       +        size_t size = 2 << 4;
        
       -        buffer = malloc(sizeof(Line) * 2 << 4);
       +        buffer = malloc(sizeof(Line) * size);
        
                input[0] = '\0';
                total = matching = 1;
        
                /* read the file into an array of lines */
       -        for (; fgets(s, LINE_SIZE, stdin); total++, matching++) {
       +        for (; fgets(s, BUFSIZ, stdin); total++, matching++) {
                        if (total > size) {
                                size *= 2;
       -                        if (!realloc(buffer, size * sizeof(Line)))
       +                        if (!realloc(buffer, sizeof(Line) * size))
                                        die("realloc");
                        }
        
       +                /* empty input match everything */
       +                buffer[total]->matches = 1;
                        buffer[total]->text[strlen(s) - 1] = '\0';
       -                buffer[total]->match = 1;  /* empty input match everything */
                }
        }
        
       @@ -74,8 +93,6 @@ fill_buffer(void)
        void
        free_buffer(Line **buffer)
        {
       -        Line *next = NULL;
       -
                for (; total > 0; total--)
                        free(buffer[total - 1]->text);
        
       @@ -83,6 +100,17 @@ free_buffer(Line **buffer)
        }
        
        
       +int
       +line_matches(Line *line, char **tokv, size_t tokc)
       +{
       +        for (size_t i = 0; i < tokc; i++)
       +                if (strstr(line->text, tokv[i]) != 0)
       +                        return 0;
       +
       +        return 1;
       +}
       +
       +
        /*
         * If inc is 1, it will only check already matching lines.
         * If inc is 0, it will only check non-matching lines.
       @@ -105,131 +133,89 @@ filter_lines(int inc)
        
                /* match lines */
                matching = 0;
       -        for (int i = 0; i < total; i++) {
       +        for (size_t i = 0; i < total; i++) {
        
                        if (input[0] && strcmp(input, buffer[i]->text) == 0) {
                                buffer[i]->match = 1;
        
                        } else if ((inc && buffer[i]->match) || (!inc && !buffer[i]->match)) {
       -                        buffer[i]->match = match_line(buffer[i], tokv, tokc);
       +                        buffer[i]->match = line_matches(buffer[i], tokv, tokc);
                                matching += buffer[i]->match;
                        }
                }
        }
        
        
       -/*
       - * Return whecher the line matches every string from tokv.
       - */
        int
       -match_line(Line *line, char **tokv, size_t tokc)
       -{
       -        for (int i = 0; i < tokc; i++)
       -                if (!!strstr(buffer[i]->text, tokv[i]))
       -                        return 0;
       -
       -        return 1;
       -}
       -
       -
       -/*
       - * Seek the previous matching line, or NULL if none matches.
       - */
       -Line *
        matching_prev(int pos)
        {
       -        for (; pos > 0 && !buffer[pos]->match; pos--);
       -        return buffer[pos];
       +        for (size_t i = pos; i > 0; i--)
       +                if (buffer[i]->match)
       +                        return i;
       +        return pos;
        }
        
        
       -/*
       - * Seek the next matching line, or NULL if none matches.
       - */
       -Line *
       -matching_next(int pos)
       +int
       +matching_next(size_t pos)
        {
       -        for (; pos < total && !buffer[pos]->match; pos++);
       -        return buffer[pos];
       +        for (size_t i = pos; i < total; i++)
       +                if (buffer[i]->match)
       +                        return i;
       +
       +        return pos;
        }
        
        
       -/*
       - * Print a line to stderr.
       - */
       -void
       -draw_line(Line *line, const int cols)
       +int
       +matching_close(size_t pos)
        {
       -        char  output[LINE_SIZE] = "\033[K";
       -        int n = 0;
       -
       -        if (opt_line_numbers) {
       -                strcat(output, buffer[current] ? "\033[1;37m" : "\033[1;30m");
       -                sprintf(output + strlen(output), "%7d\033[m ", line->number);
       -        } else {
       -                strcat(output, buffer[current] ? "\033[1;31m      > " : "        ");
       -        }
       -        n += 8;
       +        if (buffer[pos]->match)
       +                return pos;
        
       -        /* highlight buffer[current] line */
       -        if (buffer[current])
       -                strcat(output, "\033[1;33m");
       +        for (size_t i = 0; i + pos < total && i <= pos; i++) {
       +                if (buffer[pos - i]->match)
       +                        return pos - i;
        
       -        /* content */
       -        strncat(output, line->content, cols - n);
       -        n += strlen(line->content);
       +                if (buffer[pos + i]->match)
       +                        return pos + i;
       +        }
        
       -        /* 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);
       -        strcat(output, "\033[m\n");
       +        return pos;
       +}
        
       -        fputs(output, stderr);
        
       +void
       +draw_line(size_t pos, const size_t cols)
       +{
       +        fprintf(stderr, pos == current ? "\033[1;31m%s" : "%s", buffer[pos]->text);
        }
        
        
       -/*
       - * Print all the lines from an array of pointer to lines.
       - *
       - * The total number oflines printed shall not excess 'count'.
       - */
        void
       -draw_lines( int count, int cols)
       +draw_lines(size_t count, size_t cols)
        {
       -        Line *line = buffer[current];
       -        int i = 0;
       -        int j = 0;
       -
       -        /* seek back from buffer[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 : first;
       -
       -        /* print up to count lines that match the input */
       -        while (line && j < count) {
       -                if (line->matches) {
       -                        draw_line(line, line == buffer[current], cols);
       -                        j++;
       +        size_t i;
       +        for (i = MAX(current, 0); i < total && i < count;) {
       +                if (buffer[i]->match) {
       +                        draw_line(i, cols);
       +                        i++;
                        }
       -
       -                line = line->next;
                }
        
                /* continue up to the end of the screen clearing it */
       -        for (; j < count; j++)
       -                fputs("\r\033[K\n", stderr);
       +        for (; i < count; i++)
       +                fputs("\n\033[K", stderr);
       +}
       +
       +
       +void
       +draw_prompt(int cols)
       +{
       +        fprintf(stderr, "\r\033[K\033[1m%7s %s", opt_prompt, input);
        }
        
        
       -/*
       - * Update the screen interface and print all candidates.
       - *
       - * This also has to clear the previous lines.
       - */
        void
        draw_screen( int tty_fd)
        {
       @@ -261,72 +247,49 @@ draw_clear(int lines)
        }
        
        
       -/*
       - * Print the prompt, before the input, with the number of candidates that
       - * match.
       - */
        void
       -draw_prompt(int cols)
       +remove_word_input()
        {
       -        size_t  i;
       -        int     matching = matching;
       -        int     total    = total;
       -
       -        /* 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);
       +        size_t len = strlen(input) - 1;
        
       -        /* save the cursor position at the end of the input */
       -        fputs("\033[s", stderr);
       +        for (int i = len; i >= 0 && isspace(input[i]); i--)
       +                input[i] = '\0';
        
       -        /* grey */
       -        fputs("\033[1;30m", stderr);
       +        len = strlen(input) - 1;
       +        for (int i = len; i >= 0 && !isspace(input[i]); i--)
       +                input[i] = '\0';
       +}
        
       -        /* 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);
       +void
       +add_character(char key)
       +{
       +        size_t len = strlen(input);
        
       -        /* restore cursor position at the end of the input */
       -        fputs("\033[m\033[u", stderr);
       +        if (isprint(key)) {
       +                input[len]     = key;
       +                input[len + 1] = '\0';
       +        }
        
       +        filter_lines(1);
       +        current = matching_close(current);
        }
        
        
        /*
       - * Listen for the user input and call the appropriate functions.
       + * Send the selection to stdout.
         */
       -int
       -input_get(int tty_fd)
       +void
       +print_selection(int return_input)
        {
       -        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);
       -
       -        while ((exit_code = input_key(tty_fp)) == CONTINUE)
       -                draw_screen(tty_fd);
       -
       -        /* resets the terminal to the previous state. */
       -        tcsetattr(tty_fd, TCSANOW, &termio_old);
       +        fputs("\r\033[K", stderr);
        
       -        fclose(tty_fp);
       +        if (return_input || !matching) {
       +                puts(input);
        
       -        return exit_code;
       +        } else if (matching > 0) {
       +                puts(buffer[current]->text);
       +        }
        }
        
        
       @@ -341,7 +304,7 @@ input_key(FILE *tty_fp)
                char key = fgetc(tty_fp);
        
                if (key == '\n') {
       -                action_print_selection(0);
       +                print_selection(0);
                        return EXIT_SUCCESS;
                }
        
       @@ -355,12 +318,10 @@ input_key(FILE *tty_fp)
                        input[0] = '\0';
                        current = 0;
                        filter_lines(0);
       -                action_jump(1);
       -                action_jump(-1);
                        break;
        
                case CONTROL('W'):
       -                action_remove_word_input();
       +                remove_word_input();
                        filter_lines(0);
                        break;
        
       @@ -368,15 +329,15 @@ input_key(FILE *tty_fp)
                case CONTROL('H'):  /* backspace */
                        input[strlen(input) - 1] = '\0';
                        filter_lines(0);
       -                action_jump(0);
       +                current = matching_close(current);
                        break;
        
                case CONTROL('N'):
       -                action_jump(1);
       +                current = matching_next(current);
                        break;
        
                case CONTROL('P'):
       -                action_jump(-1);
       +                matching_prev(current);
                        break;
        
                case CONTROL('I'):  /* tab */
       @@ -386,52 +347,11 @@ input_key(FILE *tty_fp)
        
                case CONTROL('J'):
                case CONTROL('M'):  /* enter */
       -                action_print_selection(0);
       -                return EXIT_SUCCESS;
       -
       -        case CONTROL('@'):  /* ctrl + space */
       -                action_print_selection(1);
       +                print_selection(0);
                        return EXIT_SUCCESS;
        
       -        case CONTROL('['):  /* escape */
       -                switch (fgetc(tty_fp)) {
       -
       -                case 'O':  /* arrow keys */
       -                        switch (fgetc(tty_fp)) {
       -
       -                        case 'A':  /* up */
       -                                action_jump(-1);
       -                                break;
       -
       -                        case 'B':  /* Down */
       -                                action_jump(1);
       -                                break;
       -                        }
       -                        break;
       -
       -                case '[':  /* page control */
       -                        key = fgetc(tty_fp);
       -                        switch(fgetc(tty_fp)) {
       -
       -                        case '~':
       -                                switch (key) {
       -
       -                                case '5': /* page up */
       -                                        action_jump(-10);
       -                                        break;
       -
       -                                case '6': /* page down */
       -                                        action_jump(10);
       -                                        break;
       -                                }
       -                                break;
       -                        }
       -                        break;
       -                }
       -                break;
       -
                default:
       -                action_add_character(key);
       +                add_character(key);
                }
        
                return CONTINUE;
       @@ -439,129 +359,26 @@ input_key(FILE *tty_fp)
        
        
        /*
       - * Set the buffer[current] line to next/previous/any matching line.
       - */
       -void
       -action_jump(int direction)
       -{
       -        Line * line   = buffer[current];
       -        Line * result = line;
       -
       -        if (direction == 0 && !buffer[current]->match) {
       -                line   =               matching_next(current);
       -                line   = line ? line : matching_prev(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()
       -{
       -        size_t len = strlen(input) - 1;
       -
       -        for (int i = len; i >= 0 && isspace(input[i]); i--)
       -                input[i] = '\0';
       -
       -        len = strlen(input) - 1;
       -        for (int i = len; i >= 0 && !isspace(input[i]); i--)
       -                input[i] = '\0';
       -}
       -
       -
       -/*
       - * Add a character to the buffer input and filter lines again.
       - */
       -void
       -action_add_character(char key)
       -{
       -        size_t len = strlen(input);
       -
       -        if (isprint(key)) {
       -                input[len]     = key;
       -                input[len + 1] = '\0';
       -        }
       -
       -        filter_lines(1);
       -
       -        action_jump(0);
       -}
       -
       -
       -/*
       - * Send the selection to stdout.
       - */
       -void
       -action_print_selection(int return_input)
       -{
       -        fputs("\r\033[K", stderr);
       -
       -        if (return_input || !matching) {
       -                puts(input);
       -
       -        } else if (matching > 0) {
       -                puts(buffer[current]->text);
       -        }
       -}
       -
       -
       -opt_line_numbers  = 0;
       -opt_print_number  = 0;
       -opt_lines         = 30;
       -opt_prompt        = "";
       -
       -
       -/*
       - * 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.
       + * Listen for the user input and call the appropriate functions.
         */
       -struct termios
       -set_terminal(int tty_fd)
       +int
       +input_get(int tty_fd)
        {
       -        struct termios termio_old;
       -        struct termios termio_new;
       +        FILE *tty_fp = fopen("/dev/tty", "r");
       +        int   exit_code;
        
       -        /* set the terminal to send one key at a time. */
       +        /* receive one character at a time from the terminal */
       +        struct termios termio_old = set_terminal(tty_fd);
        
       -        /* get the terminal's state */
       -        if (tcgetattr(tty_fd, &termio_old) < 0)
       -                die("Can not get terminal attributes with tcgetattr().");
       +        while ((exit_code = input_key(tty_fp)) == CONTINUE)
       +                draw_screen(tty_fd);
        
       -        /* create a new modified state by switching the binary flags */
       -        termio_new          = termio_old;
       -        termio_new.c_lflag &= ~(ICANON | ECHO | IGNBRK);
       +        /* resets the terminal to the previous state. */
       +        tcsetattr(tty_fd, TCSANOW, &termio_old);
        
       -        /* apply this state to buffer[current] terminal now (TCSANOW) */
       -        tcsetattr(tty_fd, TCSANOW, &termio_new);
       +        fclose(tty_fp);
        
       -        return termio_old;
       +        return exit_code;
        }
        
        
       @@ -578,8 +395,6 @@ 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));
        
        
                /* command line arguments */
       @@ -588,9 +403,6 @@ main(int argc, char *argv[])
                                usage();
        
                        switch (argv[i][1]) {
       -                case 'n':
       -                        opt_line_numbers = 1;
       -                        break;
                        case 'l':
                                if (sscanf(argv[++i], "%d", &opt_lines) <= 0)
                                        die("wrong number format after -l");
       @@ -606,7 +418,7 @@ main(int argc, char *argv[])
                }
        
                /* command line arguments */
       -        buffer = fill_buffer();
       +        fill_buffer();
        
                /* set the interface */
                draw_screen(tty_fd);
       @@ -618,7 +430,6 @@ main(int argc, char *argv[])
        
                /* close files descriptors and pointers, and free memory */
                close(tty_fd);
       -        free(opt);
                free_buffer(buffer);
        
                return exit_code;