single file implementation - 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 35d50bc1a88b26d1f57219a9f3f7b1a907c57498
(DIR) parent 60947ffd7f79328a77d58ca78da7ff46c602fd1f
(HTM) Author: Josuah Demangeonā ā µ <mail@josuah.net>
Date: Thu, 16 Mar 2017 18:34:21 +0100
single file implementation
Diffstat:
M Makefile | 2 --
D TODO | 8 --------
D buffer.c | 119 -------------------------------
D draw.c | 158 -------------------------------
D input.c | 221 -------------------------------
D iomenu.1 | 78 -------------------------------
M iomenu.c | 522 ++++++++++++++++++++++++++++++-
D iomenu.h | 61 -------------------------------
8 files changed, 516 insertions(+), 653 deletions(-)
---
(DIR) diff --git a/Makefile b/Makefile
@@ -3,8 +3,6 @@ OBJ = ${SRC:.c=.o}
all: clean iomenu
-iomenu: buffer.c draw.c input.c
-
clean:
rm -f iomenu ${OBJ}
(DIR) diff --git a/TODO b/TODO
@@ -1,8 +0,0 @@
-- 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
@@ -1,119 +0,0 @@
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "iomenu.h"
-
-
-/*
- * Fill the buffer apropriately with the lines
- */
-void
-fill_buffer(void)
-{
- extern Line **buffer;
-
- char s[LINE_SIZE];
- size_t size = 1;
-
- buffer = malloc(sizeof(Line) * 2 << 4);
-
- input[0] = '\0';
- total = matching = 1;
-
- /* read the file into an array of lines */
- for (; fgets(s, LINE_SIZE, stdin); total++, matching++) {
- if (total > size) {
- size *= 2;
- if (!realloc(buffer, size * sizeof(Line)))
- die("realloc");
- }
-
- buffer[total]->text[strlen(s) - 1] = '\0';
- buffer[total]->match = 1; /* empty input match everything */
- }
-}
-
-
-void
-free_buffer(Line **buffer)
-{
- Line *next = NULL;
-
- for (; total > 0; total--)
- free(buffer[total - 1]->text);
-
- free(buffer);
-}
-
-
-/*
- * If inc is 1, it will only check already matching lines.
- * If inc is 0, it will only check non-matching lines.
- */
-void
-filter_lines(int inc)
-{
- char **tokv = NULL;
- char *s, buf[sizeof(input)];
- size_t n = 0, tokc = 0;
-
- /* tokenize input from space characters, this comes from dmenu */
- strcpy(buf, input);
- for (s = strtok(buf, " "); s; s = strtok(NULL, " ")) {
- if (++tokc > n && !(tokv = realloc(tokv, ++n * sizeof(*tokv))))
- die("realloc");
-
- tokv[tokc - 1] = s;
- }
-
- /* match lines */
- matching = 0;
- for (int 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);
- 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];
-}
-
-
-/*
- * Seek the next matching line, or NULL if none matches.
- */
-Line *
-matching_next(int pos)
-{
- for (; pos < total && !buffer[pos]->match; pos++);
- return buffer[pos];
-}
(DIR) diff --git a/draw.c b/draw.c
@@ -1,158 +0,0 @@
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <sys/ioctl.h>
-
-#include "iomenu.h"
-
-
-/*
- * Print a line to stderr.
- */
-void
-draw_line(Line *line, const int cols)
-{
- 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;
-
- /* highlight buffer[current] line */
- if (buffer[current])
- strcat(output, "\033[1;33m");
-
- /* content */
- strncat(output, line->content, cols - n);
- n += strlen(line->content);
-
- /* 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");
-
- fputs(output, stderr);
-
-}
-
-
-/*
- * 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)
-{
- 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++;
- }
-
- 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( int tty_fd)
-{
- 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(count, w.ws_col);
-
- /* go up to the prompt position and update it */
- fprintf(stderr, "\033[%dA", count + 1);
- draw_prompt(w.ws_col);
-}
-
-
-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(int cols)
-{
- 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);
-
- /* 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);
-
-}
(DIR) diff --git a/input.c b/input.c
@@ -1,221 +0,0 @@
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <termios.h>
-
-#include "iomenu.h"
-
-
-/*
- * Listen for the user input and call the appropriate functions.
- */
-int
-input_get(int tty_fd)
-{
- 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);
-
- fclose(tty_fp);
-
- return exit_code;
-}
-
-
-/*
- * Perform action associated with key
- */
-int
-input_key(FILE *tty_fp)
-{
- extern char *input;
-
- char key = fgetc(tty_fp);
-
- if (key == '\n') {
- action_print_selection(0);
- return EXIT_SUCCESS;
- }
-
- switch (key) {
-
- case CONTROL('C'):
- draw_clear(opt_lines);
- return EXIT_FAILURE;
-
- case CONTROL('U'):
- input[0] = '\0';
- current = 0;
- filter_lines(0);
- action_jump(1);
- action_jump(-1);
- break;
-
- case CONTROL('W'):
- action_remove_word_input();
- filter_lines(0);
- break;
-
- case 127:
- case CONTROL('H'): /* backspace */
- input[strlen(input) - 1] = '\0';
- filter_lines(0);
- action_jump(0);
- break;
-
- case CONTROL('N'):
- action_jump(1);
- break;
-
- case CONTROL('P'):
- action_jump(-1);
- break;
-
- case CONTROL('I'): /* tab */
- strcpy(input, buffer[current]->text);
- filter_lines(1);
- break;
-
- case CONTROL('J'):
- case CONTROL('M'): /* enter */
- action_print_selection(0);
- return EXIT_SUCCESS;
-
- case CONTROL('@'): /* ctrl + space */
- action_print_selection(1);
- 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);
- }
-
- return CONTINUE;
-}
-
-
-/*
- * 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);
- }
-}
(DIR) diff --git a/iomenu.1 b/iomenu.1
@@ -1,78 +0,0 @@
-.Dd Mars 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/iomenu.c b/iomenu.c
@@ -1,8 +1,3 @@
-opt_line_numbers = 0;
-opt_print_number = 0;
-opt_lines = 30;
-opt_prompt = "";
-
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
@@ -13,7 +8,522 @@ opt_prompt = "";
#include <sys/ioctl.h>
-#include "iomenu.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;
+
+
+/*--- functions --------------------------------------------------------------*/
+
+/*
+ * Fill the buffer apropriately with the lines
+ */
+void
+fill_buffer(void)
+{
+ extern Line **buffer;
+
+ char s[LINE_SIZE];
+ size_t size = 1;
+
+ buffer = malloc(sizeof(Line) * 2 << 4);
+
+ input[0] = '\0';
+ total = matching = 1;
+
+ /* read the file into an array of lines */
+ for (; fgets(s, LINE_SIZE, stdin); total++, matching++) {
+ if (total > size) {
+ size *= 2;
+ if (!realloc(buffer, size * sizeof(Line)))
+ die("realloc");
+ }
+
+ buffer[total]->text[strlen(s) - 1] = '\0';
+ buffer[total]->match = 1; /* empty input match everything */
+ }
+}
+
+
+void
+free_buffer(Line **buffer)
+{
+ Line *next = NULL;
+
+ for (; total > 0; total--)
+ free(buffer[total - 1]->text);
+
+ free(buffer);
+}
+
+
+/*
+ * If inc is 1, it will only check already matching lines.
+ * If inc is 0, it will only check non-matching lines.
+ */
+void
+filter_lines(int inc)
+{
+ char **tokv = NULL;
+ char *s, buf[sizeof(input)];
+ size_t n = 0, tokc = 0;
+
+ /* tokenize input from space characters, this comes from dmenu */
+ strcpy(buf, input);
+ for (s = strtok(buf, " "); s; s = strtok(NULL, " ")) {
+ if (++tokc > n && !(tokv = realloc(tokv, ++n * sizeof(*tokv))))
+ die("realloc");
+
+ tokv[tokc - 1] = s;
+ }
+
+ /* match lines */
+ matching = 0;
+ for (int 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);
+ 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];
+}
+
+
+/*
+ * Seek the next matching line, or NULL if none matches.
+ */
+Line *
+matching_next(int pos)
+{
+ for (; pos < total && !buffer[pos]->match; pos++);
+ return buffer[pos];
+}
+
+
+/*
+ * Print a line to stderr.
+ */
+void
+draw_line(Line *line, const int cols)
+{
+ 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;
+
+ /* highlight buffer[current] line */
+ if (buffer[current])
+ strcat(output, "\033[1;33m");
+
+ /* content */
+ strncat(output, line->content, cols - n);
+ n += strlen(line->content);
+
+ /* 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");
+
+ fputs(output, stderr);
+
+}
+
+
+/*
+ * 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)
+{
+ 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++;
+ }
+
+ 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( int tty_fd)
+{
+ 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(count, w.ws_col);
+
+ /* go up to the prompt position and update it */
+ fprintf(stderr, "\033[%dA", count + 1);
+ draw_prompt(w.ws_col);
+}
+
+
+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(int cols)
+{
+ 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);
+
+ /* 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);
+
+}
+
+
+/*
+ * Listen for the user input and call the appropriate functions.
+ */
+int
+input_get(int tty_fd)
+{
+ 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);
+
+ fclose(tty_fp);
+
+ return exit_code;
+}
+
+
+/*
+ * Perform action associated with key
+ */
+int
+input_key(FILE *tty_fp)
+{
+ extern char *input;
+
+ char key = fgetc(tty_fp);
+
+ if (key == '\n') {
+ action_print_selection(0);
+ return EXIT_SUCCESS;
+ }
+
+ switch (key) {
+
+ case CONTROL('C'):
+ draw_clear(opt_lines);
+ return EXIT_FAILURE;
+
+ case CONTROL('U'):
+ input[0] = '\0';
+ current = 0;
+ filter_lines(0);
+ action_jump(1);
+ action_jump(-1);
+ break;
+
+ case CONTROL('W'):
+ action_remove_word_input();
+ filter_lines(0);
+ break;
+
+ case 127:
+ case CONTROL('H'): /* backspace */
+ input[strlen(input) - 1] = '\0';
+ filter_lines(0);
+ action_jump(0);
+ break;
+
+ case CONTROL('N'):
+ action_jump(1);
+ break;
+
+ case CONTROL('P'):
+ action_jump(-1);
+ break;
+
+ case CONTROL('I'): /* tab */
+ strcpy(input, buffer[current]->text);
+ filter_lines(1);
+ break;
+
+ case CONTROL('J'):
+ case CONTROL('M'): /* enter */
+ action_print_selection(0);
+ return EXIT_SUCCESS;
+
+ case CONTROL('@'): /* ctrl + space */
+ action_print_selection(1);
+ 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);
+ }
+
+ return CONTINUE;
+}
+
+
+/*
+ * 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 = "";
/*
(DIR) diff --git a/iomenu.h b/iomenu.h
@@ -1,61 +0,0 @@
-/*--- 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;
-
-
-/*--- functions --------------------------------------------------------------*/
-
-/* iomenu */
-void die(const char *);
-struct termios set_terminal(int);
-void usage(void);
-
-/* buffer */
-void fill_buffer(void);
-void free_buffer();
-Line * add_line(int, char *, char *, Line *);
-Line * new_line(char *, char *);
-Line * matching_next(int);
-Line * matching_prev(int);
-int match_line(Line *, char **, size_t);
-void filter_lines(int);
-
-/* draw */
-void draw_screen(int);
-void draw_clear(int);
-void draw_line(Line *, int);
-void draw_lines(int, int);
-void draw_prompt(int);
-
-/* input */
-int input_get(int);
-int input_key(FILE *);
-void action_jump(int);
-void action_print_selection(int);
-void action_remove_word_input();
-void action_add_character(char);