import from repo.or.cz (sadly) - 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 d0e21509afe9e6ebf8e3f0e75ce31d9799905c7a
(HTM) 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: [1mInstalling tmux[0m
+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: [1mUpdating index in /home/josuah/.local/tmux[0m
+build: [1mRemoving broken links from /home/josuah/.local[0m
(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;
+}