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;