tmenu

Terminal menu for selecting items from stdin
git clone https://git.sinitax.com/sinitax/tmenu
Log | Files | Refs | README | LICENSE | sfeed.txt

commit 15c8e7fb77a4e9211618c0b5471d1b3e79da1f07
parent 20e32bf4ca0737affcf3035aa2300b7c9cb5a377
Author: Louis Burda <quent.burda@gmail.com>
Date:   Tue, 20 Jun 2023 15:04:04 +0200

Load entries into memory instead of seeking file

Diffstat:
M.gitignore | 1+
Mtmenu.c | 374++++++++++++++++++++++++++++++++++++++-----------------------------------------
2 files changed, 181 insertions(+), 194 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,4 @@ tmenu .cache compile_commands.json +.gdb_history diff --git a/tmenu.c b/tmenu.c @@ -1,19 +1,17 @@ -#include <stddef.h> +#include <sys/ioctl.h> #include <unistd.h> #include <termios.h> -#include <sys/ioctl.h> -#include <err.h> - +#include <fcntl.h> #include <stdbool.h> -#include <stdlib.h> -#include <stdio.h> #include <stdarg.h> #include <string.h> +#include <stdio.h> +#include <stdlib.h> #define ARRLEN(x) (sizeof(x)/sizeof((x)[0])) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define eprintf(...) fprintf(stderr, __VA_ARGS__) +#define EPRINTF(...) fprintf(stderr, __VA_ARGS__) #define KEY_CTRL(c) (((int) (c)) & 0b11111) @@ -113,14 +111,16 @@ static const struct mode modes[] = { } }; -static FILE *infile = NULL; +static char *input = NULL; +static size_t input_len = 0; +static size_t input_cap = 0; -static size_t *entries = NULL; -static size_t entries_cap = 0; -static size_t entries_cnt = 0; +static size_t *delims = NULL; +static size_t delims_cnt = 0; +static size_t delims_cap = 0; static ssize_t selected = -1; -static char *entry = NULL; +static const char *entry = NULL; static char searchbuf[1024]; static size_t searchlen = 0; @@ -137,47 +137,58 @@ static bool multiout = false; static bool verbose = false; static bool prompt = true; -static char -lower(char c) +static char delim = '\n'; + +static void +die(const char *fmt, ...) { - if (c >= 'A' && c <= 'Z') - c += 'a' - 'A'; - return c; + va_list ap; + + va_start(ap, fmt); + fputs("tmenu: ", stderr); + vfprintf(stderr, fmt, ap); + if (*fmt && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + va_end(ap); + + exit(1); } -static int -search_cmp(const char *a, const char *b, size_t size) +static void * +addcap(void *alloc, size_t dsize, size_t min, size_t *cap) { - size_t i; - - for (i = 0; i < size; i++) { - if (searchcase == CASE_SENSITIVE) { - if (a[i] != b[i]) - return a[i] - b[i]; - } else { - if (lower(a[i]) != lower(b[i])) - return lower(a[i]) - lower(b[i]); - } + if (min > *cap) { + *cap = *cap * 2; + if (*cap < min) *cap = min; + alloc = realloc(alloc, dsize * *cap); + if (!alloc) die("realloc:"); } - return 0; + return alloc; } -static const char* -search_find(const char *a, char c, size_t size) +static inline char +lower(char c) { - size_t i; + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + return c; +} - for (i = 0; i < size; i++) { - if (searchcase == CASE_SENSITIVE) { - if (a[i] == c) return a + i; - } else if (searchcase == CASE_INSENSITIVE) { - if (lower(a[i]) == lower(c)) - return a + i; - } - } +static size_t +entry_len(size_t index) +{ + return delims[index] - (index > 0 ? delims[index-1] + 1 : 0); +} - return NULL; +static inline char * +get_entry(size_t index) +{ + return input + (index > 0 ? delims[index-1] + 1 : 0); } static int @@ -210,49 +221,39 @@ readkey(FILE *f) return KEY_NONE; } -static size_t -entry_len(size_t index) +static int +search_cmp(const char *a, const char *b, size_t size) { - return entries[index + 1] - entries[index]; -} + size_t i; -static char * -read_entry(char *buf, size_t index) -{ - size_t nleft, nread; - char *pos, *tok; - - fseek(infile, (ssize_t) entries[index], SEEK_SET); - - nleft = entry_len(index); - buf = realloc(buf, nleft); - if (!buf) err(1, "realloc"); - - pos = buf; - while (nleft > 0) { - nread = fread(pos, 1, nleft, infile); - if (!nread) break; - if (nread < 0) err(1, "fread"); - nleft -= nread; - pos += nread; + for (i = 0; i < size; i++) { + if (searchcase == CASE_SENSITIVE) { + if (a[i] != b[i]) + return a[i] - b[i]; + } else { + if (lower(a[i]) != lower(b[i])) + return lower(a[i]) - lower(b[i]); + } } - tok = memchr(buf, '\n', entry_len(index)); - if (tok) *tok = '\0'; - - return buf; + return 0; } -static void -add_entry(size_t pos) +static const char* +search_find(const char *a, char c, size_t size) { - if (entries_cnt >= entries_cap) { - entries_cap *= 2; - entries = realloc(entries, - entries_cap * sizeof(size_t)); - if (!entries) err(1, "realloc"); + size_t i; + + for (i = 0; i < size; i++) { + if (searchcase == CASE_SENSITIVE) { + if (a[i] == c) return a + i; + } else if (searchcase == CASE_INSENSITIVE) { + if (lower(a[i]) == lower(c)) + return a + i; + } } - entries[entries_cnt] = pos; + + return NULL; } static void @@ -269,38 +270,38 @@ browse_prompt(void) i = (ssize_t) selected - (ssize_t) bwdctx; for (; i <= (ssize_t) selected + (ssize_t) fwdctx; i++) { - eprintf(CSI_CLEAR_LINE); + EPRINTF(CSI_CLEAR_LINE); if (i == selected) - eprintf(CSI_STYLE_BOLD); + EPRINTF(CSI_STYLE_BOLD); if (prompt) { if (i == selected) { - eprintf("(browse): "); + EPRINTF("(browse): "); } else { - eprintf("%*.s", 10, " "); + EPRINTF("%*.s", 10, " "); } } - if (selected >= 0 && i >= 0 && i < entries_cnt) { - entry = read_entry(entry, (size_t) i); + if (selected >= 0 && i >= 0 && i < delims_cnt) { + entry = get_entry((size_t) i); entlen = entry_len((size_t) i); if (entlen > linew) { - eprintf(" ..%.*s\n", (int) (linew - 3), + EPRINTF(" ..%.*s\n", (int) (linew - 3), entry + entlen - (linew - 3)); } else { - eprintf("%.*s\n", (int) linew, entry); + EPRINTF("%.*s\n", (int) linew, entry); } } else { - eprintf("\n"); + EPRINTF("\n"); } if (i == selected) - eprintf(CSI_STYLE_RESET); + EPRINTF(CSI_STYLE_RESET); } for (i = 0; i < bwdctx + fwdctx + 1; i++) - eprintf(CSI_CUR_UP); + EPRINTF(CSI_CUR_UP); } static bool @@ -313,7 +314,7 @@ browse_handlekey(int c) selected = 0; break; case 'G': - selected = (ssize_t) entries_cnt - 1; + selected = (ssize_t) delims_cnt - 1; break; case 'q': return true; @@ -326,17 +327,17 @@ browse_handlekey(int c) break; case KEY_PGDN: cnt = fwdctx + bwdctx + 1; - if (selected < entries_cnt - cnt) + if (selected < delims_cnt - cnt) selected += (ssize_t) cnt; else - selected = (ssize_t) entries_cnt - 1; + selected = (ssize_t) delims_cnt - 1; break; case KEY_UP: if (selected != 0) selected--; break; case KEY_DOWN: - if (selected != entries_cnt - 1) + if (selected != delims_cnt - 1) selected++; break; } @@ -350,9 +351,9 @@ browse_cleanup(void) size_t i; for (i = 0; i < bwdctx + 1 + fwdctx; i++) - eprintf(CSI_CLEAR_LINE "\n"); + EPRINTF(CSI_CLEAR_LINE "\n"); for (i = 0; i < bwdctx + 1 + fwdctx; i++) - eprintf(CSI_CUR_UP); + EPRINTF(CSI_CUR_UP); } static void @@ -375,7 +376,7 @@ search_prompt(void) len = snprintf(promptbuf, sizeof(promptbuf), "(search[%c:%s]) %.*s", (searchcase == CASE_SENSITIVE) ? 'I' : 'i', searchmodes[searchmode].sh, (int) searchlen, searchbuf); - if (len < 0) err(1, "snprintf"); + if (len < 0) die("snprintf:"); linew = termw; if (prompt) linew -= (size_t) len + 3; @@ -395,36 +396,36 @@ search_prompt(void) index = -1; } - eprintf(CSI_CLEAR_LINE); + EPRINTF(CSI_CLEAR_LINE); - if (i == 0) eprintf(CSI_STYLE_BOLD); + if (i == 0) EPRINTF(CSI_STYLE_BOLD); if (prompt) { if (i == 0) { - eprintf("%s : ", promptbuf); + EPRINTF("%s : ", promptbuf); } else { - eprintf("%*.s", (int) (len + 3), " "); + EPRINTF("%*.s", (int) (len + 3), " "); } } if (index < 0) { - eprintf("\n"); + EPRINTF("\n"); } else { entlen = (ssize_t) entry_len((size_t) index); - entry = read_entry(entry, (size_t) index); + entry = get_entry((size_t) index); if (entlen > linew) { - eprintf(" ..%.*s\n", (int) (linew - 3), entry + EPRINTF(" ..%.*s\n", (int) (linew - 3), entry + MAX(0, (size_t) entlen - (linew - 3))); } else { - eprintf("%.*s\n", (int) linew, entry); + EPRINTF("%.*s\n", (int) linew, entry); } } - if (i == 0) eprintf(CSI_STYLE_RESET); + if (i == 0) EPRINTF(CSI_STYLE_RESET); } for (i = 0; i < bwdctx + fwdctx + 1; i++) - eprintf(CSI_CUR_UP); + EPRINTF(CSI_CUR_UP); } static bool @@ -470,9 +471,9 @@ search_cleanup(void) size_t i; for (i = 0; i < bwdctx + 1 + fwdctx; i++) - eprintf(CSI_CLEAR_LINE "\n"); + EPRINTF(CSI_CLEAR_LINE "\n"); for (i = 0; i < bwdctx + 1 + fwdctx; i++) - eprintf(CSI_CUR_UP); + EPRINTF(CSI_CUR_UP); } static ssize_t @@ -491,18 +492,18 @@ search_match_substr(ssize_t start, int dir, if (!searchlen) { index = start + dir * (ssize_t) (new + cnt - 1); - if (index < 0 || index >= entries_cnt) + if (index < 0 || index >= delims_cnt) return fallback; return index; } found = 0; - for (i = new; i < entries_cnt; i++) { + for (i = new; i < delims_cnt; i++) { index = start + dir * (ssize_t) i; - if (index < 0 || index >= entries_cnt) + if (index < 0 || index >= delims_cnt) break; - entry = read_entry(entry, (size_t) index); + entry = get_entry((size_t) index); end = entry + entry_len((size_t) index); for (bp = entry; *bp; bp++) { @@ -528,18 +529,18 @@ search_match_fuzzy(ssize_t start, int dir, if (!searchlen) { index = start + dir * (ssize_t) (new + cnt - 1); - if (index < 0 || index >= entries_cnt) + if (index < 0 || index >= delims_cnt) return fallback; return index; } found = 0; - for (i = new; i < entries_cnt; i++) { + for (i = new; i < delims_cnt; i++) { index = start + dir * (ssize_t) i; - if (index < 0 || index >= entries_cnt) + if (index < 0 || index >= delims_cnt) break; - entry = read_entry(entry, (size_t) index); + entry = get_entry((size_t) index); end = entry + entry_len((size_t) index); pos = entry; @@ -558,88 +559,64 @@ search_match_fuzzy(ssize_t start, int dir, } static void -load_entries(const char *filepath) +load(int fd) { - size_t pos, start; - size_t nread; - char iobuf[1024]; - - entries_cap = 100; - entries = calloc(entries_cap, sizeof(size_t)); - if (!entries) err(1, "alloc"); - - if (!filepath) { - infile = tmpfile(); - if (!infile) err(1, "tmpfile"); - - start = pos = 0; - while (fgets(iobuf, sizeof(iobuf), stdin)) { - nread = strlen(iobuf); - pos += nread; - if (fwrite(iobuf, 1, nread, infile) != nread) - errx(1, "fwrite to tmpfile truncated"); - if (iobuf[nread - 1] == '\n') { - add_entry(start); - entries_cnt++; - start = pos; + ssize_t nread; + size_t i; + char *c; + + while (1) { + input = addcap(input, 1, input_len + BUFSIZ, &input_cap); + nread = read(fd, input + input_len, BUFSIZ); + if (nread <= 0) break; + + c = input + input_len; + for (i = 0; i < (size_t) nread; i++, c++) { + if (*c == delim) { + *c = '\0'; + delims = addcap(delims, sizeof(size_t), + delims_cnt + 1, &delims_cap); + delims[delims_cnt++] = input_len + i; } } - add_entry(pos); - fseek(infile, 0, SEEK_SET); - - if (!freopen("/dev/tty", "r", stdin)) - err(1, "freopen tty"); - if (fread(NULL, 0, 0, infile) < 0) - err(1, "fread stdin"); - } else { - infile = fopen(filepath, "r"); - if (!infile) err(1, "fopen %s", filepath); - - start = pos = 0; - while (fgets(iobuf, sizeof(iobuf), infile)) { - nread = strlen(iobuf); - pos += nread; - if (iobuf[nread - 1] == '\n') { - add_entry(start); - entries_cnt++; - start = pos; - } - } - add_entry(pos); + input_len += (size_t) nread; + } - fseek(infile, 0, SEEK_SET); + if (!delims_cnt && input_len + || delims_cnt && delims[delims_cnt-1] != input_len-1) { + delims = addcap(delims, sizeof(size_t), + delims_cnt + 1, &delims_cap); + delims[delims_cnt++] = input_len; } + + if (verbose) + EPRINTF("Loaded %lu entries\n", delims_cnt); } static void -run(const char *filepath) +run(void) { struct termios prevterm, newterm = { 0 }; struct winsize ws = { 0 }; int c; - load_entries(filepath); - - if (verbose) - eprintf("Loaded %lu entries\n", entries_cnt); - - if (!entries_cnt) return; + if (!delims_cnt) return; - if (tcgetattr(fileno(stderr), &prevterm)) - err(1, "tcgetattr"); + if (tcgetattr(0, &prevterm)) + die("tcgetattr:"); cfmakeraw(&newterm); newterm.c_oflag |= ONLCR | OPOST; - if (tcsetattr(fileno(stdin), TCSANOW, &newterm)) - err(1, "tcsetattr"); + if (tcsetattr(0, TCSANOW, &newterm)) + die("tcsetattr:"); - eprintf(CSI_CUR_HIDE); + EPRINTF(CSI_CUR_HIDE); selected = 0; searchlen = 0; do { - if (ioctl(1, TIOCGWINSZ, &ws) != -1) + if (ioctl(2, TIOCGWINSZ, &ws) != -1) termw = ws.ws_col; modes[mode].prompt(); @@ -663,7 +640,7 @@ run(const char *filepath) mode = MODE_BROWSE; break; case KEY_CTRL('L'): - eprintf(CSI_CLEAR_SCREEN CSI_CUR_GOTO, 0, 0); + EPRINTF(CSI_CLEAR_SCREEN CSI_CUR_GOTO, 0, 0); break; case KEY_CTRL('W'): searchlen = 0; @@ -671,7 +648,7 @@ run(const char *filepath) case KEY_CTRL('J'): case '\r': if (selected < 0) break; - entry = read_entry(entry, (size_t) selected); + entry = get_entry((size_t) selected); modes[mode].cleanup(); printf("%.*s\n", (int) entry_len((size_t) selected), entry); if (!multiout) goto exit; @@ -686,11 +663,9 @@ run(const char *filepath) exit: modes[mode].cleanup(); - eprintf(CSI_CUR_SHOW); + EPRINTF(CSI_CUR_SHOW); tcsetattr(fileno(stdin), TCSANOW, &prevterm); - - fclose(infile); } static int @@ -699,7 +674,7 @@ parseopt(const char *flag, const char **args) char *end; if (flag[0] && flag[1]) { - eprintf("Invalid flag: -%s\n", flag); + EPRINTF("Invalid flag: -%s\n", flag); exit(1); } @@ -716,48 +691,59 @@ parseopt(const char *flag, const char **args) prompt = false; return 0; case 'a': + if (!*args) die("missing -a arg"); fwdctx = strtoull(*args, &end, 10); if (end && *end) goto badint; return 1; case 'b': + if (!*args) die("missing -b arg"); bwdctx = strtoull(*args, &end, 10); if (end && *end) goto badint; return 1; + case 'd': + if (!*args) die("missing -d arg"); + if (args[0][0] && args[0][1]) die("bad -d arg"); + delim = **args; + return 1; case 'h': printf("%s\n", usage); exit(0); + default: + die("unknown opt '%s'", *flag); } return 0; badint: - eprintf("Invalid int: %s\n", *args); + EPRINTF("Invalid int: %s\n", *args); exit(1); } int main(int argc, const char **argv) { - const char *filepath; - int i; + const char **arg; + int fd; setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); - filepath = NULL; - for (i = 1; i < argc; i++) { - if (*argv[i] == '-') { - i += parseopt(argv[i] + 1, &argv[i+1]); - } else if (!filepath) { - filepath = argv[i]; + for (arg = argv + 1; *arg; arg++) { + if (**arg == '-') { + arg += parseopt(*arg + 1, arg + 1); } else { - eprintf("Unexpected argument: %s\n", argv[i]); - return 0; + fd = open(*arg, O_RDONLY); + if (fd < 0) die("open '%s':", *arg); + load(fd); + close(fd); } } - run(filepath); + if (!input) { + load(0); + if (!freopen("/dev/tty", "r", stdin)) + die("freopen tty:"); + } - free(entries); - free(entry); + run(); }