commit da3524d8c0c51bd61d0db6e0e03ea8ba7e2a7b73
Author: Louis Burda <quent.burda@gmail.com>
Date: Mon, 15 May 2023 15:11:03 +0200
Add initial version
Diffstat:
6 files changed, 552 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+tquery
+.cache
+compile_commands.json
+.gdb_history
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "lib/liballoc"]
+ path = lib/liballoc
+ url = git@sinitax.com:sinitax/liballoc
+[submodule "lib/libdvec"]
+ path = lib/libdvec
+ url = git@sinitax.com:sinitax/libdvec
diff --git a/Makefile b/Makefile
@@ -0,0 +1,30 @@
+PREFIX ?= /usr/local
+BINDIR ?= /bin
+
+LDLIBS = -lcurses -I lib/libdvec/include -I lib/liballoc/include
+CFLAGS = -std=c99 -Wunused-function -Wunused-variable -Wconversion
+
+all: tquery
+
+clean:
+ rm -f tquery
+
+cleanall: clean
+ make -C lib/liballoc cleanall
+ make -C lib/libdvec cleanall
+
+lib/libdvec/build/libdvec.a:
+ make -C lib/libdvec build/libdvec.a
+
+lib/liballoc/build/liballoc.a:
+ make -C lib/liballoc build/liballoc.a
+
+tquery: tquery.c lib/libdvec/build/libdvec.a lib/liballoc/build/liballoc.a
+
+install:
+ install -m755 tquery -t "$(DESTDIR)$(PREFIX)$(BINDIR)"
+
+uninstall:
+ rm -f "$(DESTDIR)$(PREFIX)$(BINDIR)/tquery"
+
+.PHONY: all clean install uninstall
diff --git a/lib/liballoc b/lib/liballoc
@@ -0,0 +1 @@
+Subproject commit 52903c46d996b927ff304bae66c522d317a218f3
diff --git a/lib/libdvec b/lib/libdvec
@@ -0,0 +1 @@
+Subproject commit 93bafd206267f9a0f26b03d60f5b52b0571bef56
diff --git a/tquery.c b/tquery.c
@@ -0,0 +1,510 @@
+#define _XOPEN_SOURCE 700
+
+#include "allocator.h"
+#include "dvec.h"
+
+#include <curses.h>
+
+#include <linux/limits.h>
+#include <sys/wait.h>
+#include <sys/select.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <termios.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define CTRL(x) ((x) & 0x1f)
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+struct listnav {
+ int wmin, wmax, wlen;
+ int sel;
+ int min, max;
+};
+
+static const struct allocator *ga = &stdlib_strict_heap_allocator;
+
+static int locate_out_fd = -1;
+static pid_t locate_pid = 0;
+
+static struct dvec locate_output;
+static struct dvec locate_delims;
+
+static struct listnav locate_nav;
+
+static char inputbuf[2048] = { 0 };
+static size_t inputlen = 0;
+
+static char errbuf[1024] = { 0 };
+static int errtimer = 0;
+
+static bool single_out = false;
+static bool debug = false;
+static char delim = '\n';
+
+static const char **cmd_argv = NULL;
+
+static void
+sigint(int sig)
+{
+ if (locate_pid > 0) {
+ kill(locate_pid, SIGKILL);
+ waitpid(locate_pid, NULL, 0);
+ locate_pid = 0;
+ } else {
+ exit(0);
+ }
+}
+
+static void __attribute__((noreturn))
+err(const char *fmt, ...)
+{
+ va_list ap;
+
+ endwin();
+
+ va_start(ap, fmt);
+ printf("tquery: ");
+ vprintf(fmt, ap);
+ printf(": %s\n", strerror(errno));
+ va_end(ap);
+
+ exit(1);
+}
+
+void
+swapfd(int fd1, int fd2)
+{
+ int fd, rc;
+
+ fd = dup(fd1);
+ if (fd < 0) err("dup");
+ rc = dup2(fd2, fd1);
+ if (rc == -1) err("dup2");
+ rc = dup2(fd, fd2);
+ if (rc == -1) err("dup2");
+ close(fd);
+}
+
+static void
+listnav_update_win(struct listnav *nav)
+{
+ nav->wmin = MAX(nav->wmax - nav->wlen, nav->min);
+ nav->wmax = MIN(nav->wmin + nav->wlen, nav->max);
+ nav->sel = MAX(MIN(nav->sel, nav->wmax - 1), nav->wmin);
+}
+
+static void
+listnav_init(struct listnav *nav)
+{
+ nav->sel = 0;
+ nav->min = 0;
+ nav->max = 0;
+ nav->wlen = 0;
+ nav->wmin = 0;
+ nav->wmax = 0;
+}
+
+static void
+listnav_update_bounds(struct listnav *nav, int min, int max)
+{
+ nav->min = min;
+ nav->max = max;
+ listnav_update_win(nav);
+}
+
+static void
+listnav_update_wlen(struct listnav *nav, int wlen)
+{
+ nav->wlen = wlen;
+ listnav_update_win(nav);
+}
+
+static void
+listnav_update_sel(struct listnav *nav, int sel)
+{
+ nav->sel = MAX(MIN(sel, nav->max - 1), nav->min);
+ if (nav->sel >= nav->wmax) {
+ nav->wmax = nav->sel + 1;
+ nav->wmin = MAX(nav->min, nav->wmax - nav->wlen);
+ } else if (nav->sel < nav->wmin) {
+ nav->wmin = nav->sel;
+ nav->wmax = MIN(nav->wmin + nav->wlen, nav->max);
+ }
+}
+
+void
+reset(void)
+{
+ dvec_clear(&locate_delims);
+ dvec_clear(&locate_output);
+
+ if (locate_out_fd >= 0) {
+ close(locate_out_fd);
+ locate_out_fd = -1;
+ }
+
+ if (locate_pid > 0) {
+ kill(locate_pid, SIGKILL);
+ waitpid(locate_pid, NULL, 0);
+ locate_pid = 0;
+ }
+}
+
+char *
+findbin(const char *name)
+{
+ char buf[PATH_MAX];
+ char *path, *tok, *ntok, *end;
+ struct dirent *ent;
+ DIR *dir;
+
+ path = getenv("PATH");
+ if (!path) return NULL;
+
+ ntok = tok = path;
+ while (ntok) {
+ ntok = strchr(tok, ':');
+ end = ntok ? ntok : tok + strlen(tok);
+ strncpy(buf, tok, (size_t) (end - tok));
+ buf[end - tok] = '\0';
+ tok = ntok + 1;
+
+ dir = opendir(buf);
+ if (!dir) continue;
+
+ while ((ent = readdir(dir))) {
+ if (!strcmp(ent->d_name, name)) {
+ closedir(dir);
+ if (strlen(buf) + 1 + strlen(name) >= PATH_MAX)
+ return NULL;
+ strcat(buf, "/");
+ strcat(buf, name);
+ return strdup(buf);
+ }
+ }
+
+ closedir(dir);
+ }
+
+ return NULL;
+}
+
+static void
+spawn(const char **prefix, const char *search_args)
+{
+ struct dvec argv;
+ const char **arg;
+ const char *tok, *ntok, *end;
+ int out_pipe[2];
+
+ if (pipe(out_pipe)) err("pipe");
+
+ locate_pid = fork();
+ if (locate_pid < 0) err("fork");
+ if (!locate_pid) {
+ close(0);
+ if (debug) {
+ swapfd(1, 2);
+ close(1);
+ } else {
+ close(1);
+ close(2);
+ }
+ dup2(out_pipe[1], 1);
+ close(out_pipe[0]);
+
+ dvec_init(&argv, sizeof(char *), 0, ga);
+ for (arg = prefix; *arg; arg++) {
+ dvec_add_back(&argv, 1);
+ *(const char **)dvec_back(&argv) = *arg;
+ }
+
+ ntok = tok = search_args;
+ while (ntok && *ntok) {
+ ntok = strchr(tok, ' ');
+ end = ntok ? ntok : tok + strlen(tok);
+ dvec_add_back(&argv, 1);
+ *(const char **)dvec_back(&argv)
+ = strndup(tok, (size_t) (end - tok));
+ tok = ntok + 1;
+ }
+
+ dvec_add_back(&argv, 1);
+ *(const char **)dvec_back(&argv) = NULL;
+
+ execv(*(const char **)dvec_front(&argv), argv.data);
+ fprintf(stderr, "tquery: execv '%s': %s",
+ *(const char **)dvec_front(&argv), strerror(errno));
+ exit(1);
+ } else {
+ close(out_pipe[1]);
+
+ locate_out_fd = out_pipe[0];
+ }
+}
+
+static void
+load()
+{
+ char buf[BUFSIZ];
+ ssize_t nread;
+ size_t i, len;
+ char *start;
+
+ nread = read(locate_out_fd, buf, BUFSIZ);
+ if (nread <= 0) {
+ locate_pid = 0;
+ close(locate_out_fd);
+ locate_out_fd = -1;
+ return;
+ }
+
+ len = dvec_len(&locate_output);
+ dvec_add_back(&locate_output, (size_t) nread);
+ start = dvec_at(&locate_output, len);
+ memcpy(start, buf, (size_t) nread);
+
+ for (i = len; i < len + (size_t) nread; i++, start++) {
+ if (*start == delim) {
+ *start = '\0';
+ dvec_add_back(&locate_delims, 1);
+ *(size_t *)dvec_back(&locate_delims) = i;
+ }
+ }
+}
+
+static char *
+entry(int i)
+{
+ size_t off;
+
+ assert(i >= 0);
+
+ if (i == 0) {
+ return dvec_at(&locate_output, 0);
+ } else {
+ off = *(size_t *)dvec_at(&locate_delims, (size_t) i - 1);
+ return dvec_at(&locate_output, off + 1);
+ }
+}
+
+static void
+update()
+{
+ const ssize_t miny = 3;
+ int width, height;
+ ssize_t i, y;
+
+ erase();
+
+ width = getmaxx(stdscr);
+ height = getmaxy(stdscr);
+
+ if (errbuf[0] && errtimer >= 0) {
+ attr_on(A_BOLD, NULL);
+ mvprintw(0, 1, "%s", errbuf);
+ attr_off(A_BOLD, NULL);
+ errtimer--;
+ }
+
+ mvprintw(1, 1, "%lu %lu", dvec_len(&locate_output),
+ dvec_len(&locate_delims));
+ mvprintw(2, 1, "> %.*s", width - 1, inputbuf);
+
+ if (!dvec_empty(&locate_output)) {
+ listnav_update_bounds(&locate_nav, 0, (int) dvec_len(&locate_delims));
+ listnav_update_wlen(&locate_nav, (int) (height - miny));
+
+ i = locate_nav.wmin;
+ for (y = miny; y < height && i < locate_nav.wmax; y++, i++) {
+ if (i == locate_nav.sel)
+ attr_on(A_REVERSE, NULL);
+
+ mvprintw((int) y, 1, "%.*s",
+ width - 1, entry((int) i));
+
+ if (i == locate_nav.sel)
+ attr_off(A_REVERSE, NULL);
+ }
+ }
+
+ move(2, (int) (1 + 2 + inputlen));
+
+ refresh();
+}
+
+static void
+input()
+{
+ bool dirty;
+ bool reload;
+ int c;
+
+ dirty = false;
+ reload = false;
+ while ((c = getch()) != ERR) {
+ switch (c) {
+ case KEY_BACKSPACE:
+ if (inputlen) inputlen--;
+ dirty = true;
+ reload = true;
+ break;
+ case KEY_PPAGE:
+ listnav_update_sel(&locate_nav,
+ locate_nav.sel - locate_nav.wlen / 2);
+ dirty = true;
+ break;
+ case KEY_NPAGE:
+ listnav_update_sel(&locate_nav,
+ locate_nav.sel + locate_nav.wlen / 2);
+ dirty = true;
+ break;
+ case KEY_DOWN:
+ listnav_update_sel(&locate_nav, locate_nav.sel + 1);
+ dirty = true;
+ break;
+ case KEY_UP:
+ listnav_update_sel(&locate_nav, locate_nav.sel - 1);
+ dirty = true;
+ break;
+ case '\x1b':
+ /* TODO: word-wise seek */
+ switch (getch()) {
+ case 'c':
+ break;
+ case 'd':
+ break;
+ }
+ case CTRL('w'):
+ inputlen = 0;
+ dirty = true;
+ reload = true;
+ break;
+ case '\n':
+ if (!isatty(2)) {
+ fprintf(stderr, "%s\n", entry(locate_nav.sel));
+ }
+ if (single_out)
+ exit(0);
+ break;
+ default:
+ if (c < 32 || c >= 127)
+ continue;
+ if (inputlen < sizeof(inputbuf) - 1)
+ inputbuf[inputlen++] = (char) c;
+ reload = true;
+ dirty = true;
+ break;
+ }
+ inputbuf[inputlen] = '\0';
+ }
+
+ if (dirty) {
+ update();
+ if (reload) {
+ reset();
+ spawn(cmd_argv, inputbuf);
+ }
+ }
+}
+
+void
+parse(int argc, const char **argv)
+{
+ const char **arg;
+ const char *cmd_path;
+
+ for (arg = &argv[1]; *arg; arg++) {
+ if (!strcmp(*arg, "-s")) {
+ single_out = true;
+ } else if (!strcmp(*arg, "-d")) {
+ debug = true;
+ } else if (!strcmp(*arg, "-c")) {
+ delim = **++arg;
+ } else if (!strcmp(*arg, "--")) {
+ cmd_argv = arg + 1;
+ break;
+ } else {
+ fprintf(stderr, "tquery: unknown option '%s'\n", arg[0]);
+ exit(1);
+ }
+ }
+
+ if (!cmd_argv || !*cmd_argv) {
+ fprintf(stderr, "tquery: empty command args\n");
+ exit(1);
+ }
+
+ if (*cmd_argv[0] != '/' && strncmp(cmd_argv[0], "./", 2)) {
+ cmd_path = findbin(cmd_argv[0]);
+ if (!cmd_path) {
+ fprintf(stderr, "tquery: no '%s' in PATH\n", cmd_argv[0]);
+ exit(1);
+ }
+ cmd_argv[0] = cmd_path;
+ }
+}
+
+int
+main(int argc, const char **argv)
+{
+ int maxfd, rc;
+ fd_set set;
+
+ parse(argc, argv);
+
+ swapfd(1, 2);
+
+ dvec_init(&locate_output, 1, 1024 * 1024 * 25, ga);
+ dvec_init(&locate_delims, sizeof(size_t), 1024, ga);
+ listnav_init(&locate_nav);
+
+ signal(SIGINT, sigint);
+
+ atexit((void (*)()) endwin);
+ initscr();
+ cbreak();
+ noecho();
+ keypad(stdscr, true);
+
+ timeout(0);
+
+ update();
+
+ reset();
+ spawn(cmd_argv, "");
+
+ inputlen = 0;
+ while (1) {
+ maxfd = 0;
+ FD_ZERO(&set);
+ FD_SET(0, &set);
+ if (locate_pid) {
+ FD_SET(locate_out_fd, &set);
+ if (locate_out_fd > maxfd)
+ maxfd = locate_out_fd;
+ }
+ rc = select(maxfd+1, &set, NULL, NULL, NULL);
+ if (rc <= 0 && errno == EINTR) {
+ update();
+ continue;
+ } else if (rc <= 0) err("select");
+
+ if (FD_ISSET(0, &set)) {
+ input();
+ } else if (FD_ISSET(locate_out_fd, &set)) {
+ load();
+ update();
+ }
+ }
+}