aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Draw.c49
-rw-r--r--src/Draw.h6
-rw-r--r--src/Globals.h34
-rw-r--r--src/Input.c112
-rw-r--r--src/Input.h7
-rw-r--r--src/Main.c112
-rw-r--r--src/Terminal.c75
-rw-r--r--src/Terminal.h12
8 files changed, 407 insertions, 0 deletions
diff --git a/src/Draw.c b/src/Draw.c
new file mode 100644
index 0000000..1132f10
--- /dev/null
+++ b/src/Draw.c
@@ -0,0 +1,49 @@
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "Globals.h"
+#include "Terminal.h"
+#include "Draw.h"
+
+void draw(void) {
+ int rows, cols;
+ struct winsize w;
+ ioctl(tty_out_fd, TIOCGWINSZ, &w);
+ rows = w.ws_row;
+ cols = w.ws_col;
+
+ if (rows < 1) rows = 24;
+ if (cols < 1) cols = 80;
+
+ int prompt_len = strlen(opts.prompt);
+ int input_pos = prompt_len + 2;
+ int filter_line = rows;
+ int item_end = rows - 1;
+
+ dprintf(tty_out_fd, "\033[H\033[J");
+
+ dprintf(tty_out_fd, "\033[%d;1H", filter_line);
+ dprintf(tty_out_fd, "\033[7m");
+ dprintf(tty_out_fd, "%-*s", cols, "");
+ dprintf(tty_out_fd, "\033[%d;1H", filter_line);
+ dprintf(tty_out_fd, "%s", opts.prompt);
+ if (input_len > 0) {
+ dprintf(tty_out_fd, " %s", input);
+ }
+ dprintf(tty_out_fd, "\033[0m");
+
+ for (int i = 1; i < item_end; i++) {
+ int idx = i - 1 + scroll;
+ dprintf(tty_out_fd, "\033[%d;1H", i);
+ if (idx < (int)filtered.count) {
+ if (idx == cursor) {
+ dprintf(tty_out_fd, "\033[7m");
+ }
+ dprintf(tty_out_fd, "%s\033[0m", filtered.items[idx]);
+ }
+ }
+ dprintf(tty_out_fd, "\033[%d;%dH", filter_line, input_pos);
+ fsync(tty_out_fd);
+}
diff --git a/src/Draw.h b/src/Draw.h
new file mode 100644
index 0000000..82c0b7f
--- /dev/null
+++ b/src/Draw.h
@@ -0,0 +1,6 @@
+#ifndef INSEL_DRAW_H
+#define INSEL_DRAW_H
+
+void draw(void);
+
+#endif
diff --git a/src/Globals.h b/src/Globals.h
new file mode 100644
index 0000000..95a61ea
--- /dev/null
+++ b/src/Globals.h
@@ -0,0 +1,34 @@
+#ifndef INSEL_GLOBALS_H
+#define INSEL_GLOBALS_H
+
+#include <stddef.h>
+
+#define INITIAL_CAPACITY 64
+#define DEFAULT_LINES 10
+
+typedef struct {
+ char **items;
+ size_t count;
+ size_t capacity;
+} ItemList;
+
+typedef struct {
+ char *prompt;
+ int insensitive;
+} Options;
+
+extern Options opts;
+extern ItemList all_items;
+extern ItemList filtered;
+extern char *input;
+extern size_t input_len;
+extern size_t input_capacity;
+extern int cursor;
+extern int scroll;
+extern int needs_redraw;
+extern int tty_fd;
+extern int tty_out_fd;
+extern int orig_stdout;
+extern int stdin_is_tty;
+
+#endif
diff --git a/src/Input.c b/src/Input.c
new file mode 100644
index 0000000..23a474d
--- /dev/null
+++ b/src/Input.c
@@ -0,0 +1,112 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "Globals.h"
+#include "Terminal.h"
+#include "Input.h"
+
+int matches(const char *s) {
+ if (input_len == 0) return 1;
+ const char *p = s;
+ if (opts.insensitive) {
+ while (*p) {
+ if (strncasecmp(p, input, input_len) == 0) return 1;
+ p++;
+ }
+ } else {
+ while (*p) {
+ if (strncmp(p, input, input_len) == 0) return 1;
+ p++;
+ }
+ }
+ return 0;
+}
+
+void filter_items(void) {
+ filtered.count = 0;
+ for (size_t i = 0; i < all_items.count; i++) {
+ if (matches(all_items.items[i])) {
+ if (filtered.count >= filtered.capacity) {
+ size_t new_cap = filtered.capacity == 0 ? INITIAL_CAPACITY : filtered.capacity * 2;
+ filtered.items = realloc(filtered.items, new_cap * sizeof(char *));
+ filtered.capacity = new_cap;
+ }
+ filtered.items[filtered.count++] = all_items.items[i];
+ }
+ }
+ if ((size_t)cursor >= filtered.count) cursor = filtered.count > 0 ? (int)filtered.count - 1 : 0;
+ if (scroll > cursor) scroll = cursor;
+ if (scroll < cursor - DEFAULT_LINES + 1) scroll = cursor - DEFAULT_LINES + 1;
+ if (scroll < 0) scroll = 0;
+ needs_redraw = 1;
+}
+
+void handle_input(void) {
+ char c;
+ if (read(tty_fd, &c, 1) <= 0) return;
+ if (c == 27) {
+ char seq[3];
+ ssize_t n = read(tty_fd, &seq[0], 1);
+ if (n > 0 && seq[0] == '[') {
+ if (read(tty_fd, &seq[1], 1) > 0) {
+ switch (seq[1]) {
+ case 'A':
+ if (cursor > 0) cursor--;
+ break;
+ case 'B':
+ if (cursor < (int)filtered.count - 1) cursor++;
+ break;
+ case 'H':
+ case 'F':
+ cursor = 0;
+ break;
+ case 'G':
+ cursor = filtered.count > 0 ? filtered.count - 1 : 0;
+ break;
+ case '5':
+ case '6':
+ if (read(tty_fd, &seq[2], 1) > 0 && seq[2] == '~') {
+ int jump = DEFAULT_LINES;
+ if (seq[1] == '5') {
+ cursor -= jump;
+ if (cursor < 0) cursor = 0;
+ } else {
+ cursor += jump;
+ if (cursor >= (int)filtered.count) cursor = filtered.count > 0 ? (int)filtered.count - 1 : 0;
+ }
+ }
+ break;
+ }
+ }
+ } else {
+ exit(1);
+ }
+ if (cursor < scroll) scroll = cursor;
+ if (cursor > scroll + DEFAULT_LINES - 1) scroll = cursor - DEFAULT_LINES + 1;
+ needs_redraw = 1;
+ } else if (c == 127 || c == 8) {
+ if (input_len > 0) input[--input_len] = '\0';
+ filter_items();
+ } else if (c == '\n' || c == '\r') {
+ if (filtered.count > 0) {
+ dprintf(tty_out_fd, "\033[?1049l\033[2J\033[?25h\033[0m");
+ dprintf(orig_stdout, "%s\n", filtered.items[cursor]);
+ exit(0);
+ }
+ } else if (c == 4) {
+ exit(1);
+ } else if (c >= 32 && c < 127) {
+ if ((int)input_len + 2 > (int)input_capacity) {
+ size_t new_cap = input_capacity == 0 ? INITIAL_CAPACITY : input_capacity * 2;
+ input = realloc(input, new_cap);
+ memset(input + input_capacity, 0, new_cap - input_capacity);
+ input_capacity = new_cap;
+ }
+ input[input_len++] = c;
+ input[input_len] = '\0';
+ filter_items();
+ }
+}
diff --git a/src/Input.h b/src/Input.h
new file mode 100644
index 0000000..eefd6d0
--- /dev/null
+++ b/src/Input.h
@@ -0,0 +1,7 @@
+#ifndef INSEL_INPUT_H
+#define INSEL_INPUT_H
+
+void filter_items(void);
+void handle_input(void);
+
+#endif
diff --git a/src/Main.c b/src/Main.c
new file mode 100644
index 0000000..4ecd4f3
--- /dev/null
+++ b/src/Main.c
@@ -0,0 +1,112 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/select.h>
+
+#include "Globals.h"
+#include "Terminal.h"
+#include "Draw.h"
+#include "Input.h"
+
+Options opts = {
+ .prompt = "",
+ .insensitive = 0
+};
+
+ItemList all_items = {0};
+ItemList filtered = {0};
+char *input = NULL;
+size_t input_len = 0;
+size_t input_capacity = 0;
+int cursor = 0;
+int scroll = 0;
+int needs_redraw = 1;
+
+static void ensure_item_capacity(ItemList *list) {
+ if (list->count >= list->capacity) {
+ size_t new_cap = list->capacity == 0 ? INITIAL_CAPACITY : list->capacity * 2;
+ list->items = realloc(list->items, new_cap * sizeof(char *));
+ list->capacity = new_cap;
+ }
+}
+
+void read_items(void) {
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t n;
+ while ((n = getline(&line, &len, stdin)) != -1) {
+ if (n > 0 && line[n-1] == '\n') line[n-1] = '\0';
+ ensure_item_capacity(&all_items);
+ all_items.items[all_items.count++] = strdup(line);
+ }
+ free(line);
+ filter_items();
+}
+
+void version(void) {
+ puts("insel 1.0");
+ puts("'those who select their inputs'\n");
+ puts("GNU GPL version 2 <https://gnu.org/licenses/old-licenses/gpl-2.0.html>.\n"
+ "This is free software: you are free to change and redistribute it.\n"
+ "There is NO WARRANTY, to the extent permitted by law.");
+ exit(0);
+}
+
+void help(void) {
+ puts("Usage: insel [-i] [-p prompt]"
+ "\n"
+ "Options:"
+ "\n"
+ " -i Case-insensitive matching"
+ "\n"
+ " -v Show version"
+ "\n"
+ " -p prompt Prompt to display"
+ "\n"
+ " --help Show this help message");
+ exit(0);
+}
+
+int main(int argc, char *argv[]) {
+ stdin_is_tty = isatty(STDIN_FILENO);
+ orig_stdout = dup(STDOUT_FILENO);
+
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-v") == 0) version();
+ else if (strcmp(argv[i], "--help") == 0) help();
+ else if (strcmp(argv[i], "-i") == 0) opts.insensitive = 1;
+ else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-f") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
+ if (opts.prompt[0] != '\0') free(opts.prompt);
+ opts.prompt = strdup(argv[++i]);
+ }
+ else if (strcmp(argv[i], "-nb") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-nf") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-sb") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-sf") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-fn") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-m") == 0 && i + 1 < argc) i++;
+ else if (strcmp(argv[i], "-w") == 0 && i + 1 < argc) i++;
+ }
+
+ read_items();
+ setup_terminal();
+
+ while (1) {
+ if (needs_redraw) {
+ draw();
+ needs_redraw = 0;
+ }
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(tty_fd, &fds);
+ struct timeval tv = { .tv_sec = 0, .tv_usec = 10000 };
+ if (select(tty_fd + 1, &fds, NULL, NULL, &tv) > 0) {
+ handle_input();
+ }
+ }
+ return 0;
+}
diff --git a/src/Terminal.c b/src/Terminal.c
new file mode 100644
index 0000000..6cc24db
--- /dev/null
+++ b/src/Terminal.c
@@ -0,0 +1,75 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "Globals.h"
+#include "Terminal.h"
+
+static struct termios orig_term;
+
+int tty_fd = -1;
+int tty_out_fd = -1;
+int orig_stdout = -1;
+int stdin_is_tty = 0;
+
+void restore_terminal(void) {
+ if (tty_fd >= 0) {
+ tcsetattr(tty_fd, TCSAFLUSH, &orig_term);
+ }
+
+ int restore_fd = tty_out_fd >= 0 ? tty_out_fd : STDOUT_FILENO;
+ dprintf(restore_fd, "\033[?1049l\033[2J\033[?25h\033[0m");
+
+ if (tty_fd >= 0) {
+ close(tty_fd);
+ }
+ if (tty_out_fd >= 0 && tty_out_fd != STDOUT_FILENO) {
+ close(tty_out_fd);
+ }
+ if (orig_stdout >= 0 && orig_stdout != STDOUT_FILENO) {
+ close(orig_stdout);
+ }
+}
+
+static void handle_signal(int sig) {
+ restore_terminal();
+ _exit(128 + sig);
+}
+
+void setup_terminal(void) {
+ signal(SIGINT, handle_signal);
+ signal(SIGTERM, handle_signal);
+
+ if (isatty(STDOUT_FILENO)) {
+ tty_out_fd = STDOUT_FILENO;
+ } else {
+ tty_out_fd = open("/dev/tty", O_WRONLY);
+ if (tty_out_fd < 0) {
+ fprintf(stderr, "Error: cannot open terminal for output\n");
+ exit(1);
+ }
+ }
+
+ tty_fd = open("/dev/tty", O_RDWR);
+ if (tty_fd < 0) {
+ tty_fd = dup(STDIN_FILENO);
+ }
+
+ if (tty_fd < 0) {
+ dprintf(tty_out_fd, "\033[?1049l\033[2J\033[?25h");
+ close(tty_out_fd);
+ fprintf(stderr, "Error: cannot open terminal for keyboard input\n");
+ exit(1);
+ }
+
+ struct termios t;
+ tcgetattr(tty_fd, &orig_term);
+ atexit(restore_terminal);
+ tcgetattr(tty_fd, &t);
+ t.c_lflag &= ~(ICANON | ECHO);
+ tcsetattr(tty_fd, TCSAFLUSH, &t);
+ dprintf(tty_out_fd, "\033[?1049l\033[2J\033[?25l");
+}
diff --git a/src/Terminal.h b/src/Terminal.h
new file mode 100644
index 0000000..3b5fb0b
--- /dev/null
+++ b/src/Terminal.h
@@ -0,0 +1,12 @@
+#ifndef INSEL_TERMINAL_H
+#define INSEL_TERMINAL_H
+
+void setup_terminal(void);
+void restore_terminal(void);
+
+extern int tty_fd;
+extern int tty_out_fd;
+extern int orig_stdout;
+extern int stdin_is_tty;
+
+#endif