tmenu

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

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

Make prompt smaller and add top_prompt option

Diffstat:
Mtmenu.c | 160++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 97 insertions(+), 63 deletions(-)

diff --git a/tmenu.c b/tmenu.c @@ -1,4 +1,5 @@ #include <sys/ioctl.h> +#include <signal.h> #include <unistd.h> #include <termios.h> #include <fcntl.h> @@ -25,6 +26,8 @@ #define CSI_STYLE_BOLD "\x1b[1m" #define CSI_STYLE_RESET "\x1b[0m" #define CSI_CLEAR_SCREEN "\x1b[2J" +#define CSI_STYLE_ULINE "\x1b[4m" +#define CSI_STYLE_NULINE "\x1b[24m" #define CSI_CUR_GOTO "\x1b[%i%iH" enum { @@ -65,7 +68,7 @@ struct mode { }; struct searchmode { - const char *sh; + char c; ssize_t (*match)(ssize_t, int, bool, size_t, ssize_t); }; @@ -84,16 +87,13 @@ static ssize_t search_match_substr(ssize_t start, int dir, static ssize_t search_match_fuzzy(ssize_t start, int dir, bool new, size_t cnt, ssize_t fallback); -static const char *usage = \ - "Usage: tmenu [-h] [-m] [-a LINES] [-b LINES]"; - static const struct searchmode searchmodes[] = { [SEARCH_SUBSTR] = { - .sh = "SUB", + .c = 'S', .match = search_match_substr, }, [SEARCH_FUZZY] = { - .sh = "FUZ", + .c = 'F', .match = search_match_fuzzy, } }; @@ -111,6 +111,8 @@ static const struct mode modes[] = { } }; +static bool init = false; + static char *input = NULL; static size_t input_len = 0; static size_t input_cap = 0; @@ -121,6 +123,8 @@ static size_t delims_cap = 0; static ssize_t selected = -1; static const char *entry = NULL; +static size_t entry_max = 0; +static size_t entry_off = 0; static char searchbuf[1024]; static size_t searchlen = 0; @@ -135,7 +139,8 @@ static size_t termw = 80; static bool multiout = false; static bool verbose = false; -static bool prompt = true; +static bool show_prompt = true; +static bool top_prompt = false; static char delim = '\n'; @@ -158,6 +163,24 @@ die(const char *fmt, ...) exit(1); } +static void +prompt(void) +{ + struct winsize ws = { 0 }; + + if (ioctl(2, TIOCGWINSZ, &ws) != -1) + termw = ws.ws_col; + + modes[mode].prompt(); +} + +static void +sigwinch(int sig) +{ + if (init) prompt(); + signal(SIGWINCH, sigwinch); +} + static void * addcap(void *alloc, size_t dsize, size_t min, size_t *cap) { @@ -222,21 +245,20 @@ readkey(FILE *f) } static int -search_cmp(const char *a, const char *b, size_t size) +search_eq(const char *a, const char *b, size_t size) { size_t i; for (i = 0; i < size; i++) { if (searchcase == CASE_SENSITIVE) { - if (a[i] != b[i]) - return a[i] - b[i]; + if (a[i] != b[i]) return false; } else { if (lower(a[i]) != lower(b[i])) - return lower(a[i]) - lower(b[i]); + return false; } } - return 0; + return true; } static const char* @@ -259,14 +281,13 @@ search_find(const char *a, char c, size_t size) static void browse_prompt(void) { - static const char promptstr[] = "(browse): "; size_t linew, entlen; ssize_t i; if (selected < 0) selected = 0; - linew = termw; - if (prompt) linew -= ARRLEN(promptstr) - 1; + if (show_prompt && top_prompt) + EPRINTF(CSI_STYLE_BOLD "[B] " CSI_STYLE_RESET "\n"); i = (ssize_t) selected - (ssize_t) bwdctx; for (; i <= (ssize_t) selected + (ssize_t) fwdctx; i++) { @@ -275,12 +296,15 @@ browse_prompt(void) if (i == selected) EPRINTF(CSI_STYLE_BOLD); - if (prompt) { + if (show_prompt && !top_prompt) { + linew = termw - 4; if (i == selected) { - EPRINTF("(browse): "); + EPRINTF("[B] "); } else { - EPRINTF("%*.s", 10, " "); + EPRINTF(" "); } + } else { + linew = termw; } if (selected >= 0 && i >= 0 && i < delims_cnt) { @@ -300,7 +324,7 @@ browse_prompt(void) EPRINTF(CSI_STYLE_RESET); } - for (i = 0; i < bwdctx + fwdctx + 1; i++) + for (i = 0; i < bwdctx + fwdctx + 1 + show_prompt * top_prompt; i++) EPRINTF(CSI_CUR_UP); } @@ -359,10 +383,8 @@ browse_cleanup(void) static void search_prompt(void) { - char promptbuf[256]; + size_t linew, off, entlen; ssize_t i, index; - ssize_t len, entlen; - size_t linew; if (selected < 0) selected = 0; @@ -373,15 +395,16 @@ search_prompt(void) selected = search_match(selected, BWD, 1, 1, -1); } - len = snprintf(promptbuf, sizeof(promptbuf), "(search[%c:%s]) %.*s", - (searchcase == CASE_SENSITIVE) ? 'I' : 'i', - searchmodes[searchmode].sh, (int) searchlen, searchbuf); - if (len < 0) die("snprintf:"); - - linew = termw; - if (prompt) linew -= (size_t) len + 3; + if (show_prompt && top_prompt) { + EPRINTF(CSI_STYLE_BOLD "[%c] " CSI_STYLE_ULINE "%.*s" + CSI_STYLE_NULINE "%.*s\n" CSI_STYLE_RESET, + (searchcase == CASE_SENSITIVE ? + searchmodes[searchmode].c + : lower(searchmodes[searchmode].c)), + (int) searchlen, searchbuf, (int) termw, " "); + } - for (i = -(ssize_t) bwdctx; i <= fwdctx; i++) { + for (i = -(ssize_t) bwdctx; i <= (ssize_t) fwdctx; i++) { if (selected >= 0) { if (i < 0) { index = search_match(selected, @@ -400,31 +423,37 @@ search_prompt(void) if (i == 0) EPRINTF(CSI_STYLE_BOLD); - if (prompt) { + if (show_prompt && !top_prompt) { if (i == 0) { - EPRINTF("%s : ", promptbuf); + EPRINTF("[%c] " CSI_STYLE_ULINE "%.*s" + CSI_STYLE_NULINE " ", + (searchcase == CASE_SENSITIVE ? + searchmodes[searchmode].c + : lower(searchmodes[searchmode].c)), + (int) searchlen, searchbuf); } else { - EPRINTF("%*.s", (int) (len + 3), " "); + EPRINTF("%*.s", (int) (4 + searchlen + 1), " "); } + linew = (size_t) MAX(0, (ssize_t) termw + - (ssize_t) searchlen - 5); + } else { + linew = termw; } if (index < 0) { EPRINTF("\n"); } else { - entlen = (ssize_t) entry_len((size_t) index); + entlen = entry_len((size_t) index); entry = get_entry((size_t) index); - if (entlen > linew) { - EPRINTF(" ..%.*s\n", (int) (linew - 3), entry - + MAX(0, (size_t) entlen - (linew - 3))); - } else { - EPRINTF("%.*s\n", (int) linew, entry); - } + off = entlen >= linew ? entlen - linew : 0; + off = MIN(entry_off, off); + EPRINTF("%.*s\n", (int) linew, entry + off); } if (i == 0) EPRINTF(CSI_STYLE_RESET); } - for (i = 0; i < bwdctx + fwdctx + 1; i++) + for (i = 0; i < bwdctx + fwdctx + 1 + show_prompt * top_prompt; i++) EPRINTF(CSI_CUR_UP); } @@ -487,7 +516,7 @@ search_match_substr(ssize_t start, int dir, bool new, size_t cnt, ssize_t fallback) { const char *end, *bp; - size_t i, found, len; + size_t i, found; ssize_t index; if (!searchlen) { @@ -507,8 +536,8 @@ search_match_substr(ssize_t start, int dir, end = entry + entry_len((size_t) index); for (bp = entry; *bp; bp++) { - len = MIN(searchlen, (size_t) (end - bp)); - if (!search_cmp(bp, searchbuf, len)) { + if (searchlen > end - bp) continue; + if (search_eq(bp, searchbuf, searchlen)) { if (++found == cnt) return index; break; @@ -572,12 +601,12 @@ load(int fd) 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; - } + if (*c != delim) continue; + *c = '\0'; + delims = addcap(delims, sizeof(size_t), + delims_cnt + 1, &delims_cap); + delims[delims_cnt++] = input_len + i; + entry_max = MAX(entry_max, entry_len(delims_cnt-1)); } input_len += (size_t) nread; @@ -588,6 +617,7 @@ load(int fd) delims = addcap(delims, sizeof(size_t), delims_cnt + 1, &delims_cap); delims[delims_cnt++] = input_len; + entry_max = MAX(entry_max, entry_len(delims_cnt-1)); } if (verbose) @@ -598,7 +628,6 @@ static void run(void) { struct termios prevterm, newterm = { 0 }; - struct winsize ws = { 0 }; int c; if (!delims_cnt) return; @@ -613,13 +642,11 @@ run(void) EPRINTF(CSI_CUR_HIDE); + init = true; selected = 0; searchlen = 0; do { - if (ioctl(2, TIOCGWINSZ, &ws) != -1) - termw = ws.ws_col; - - modes[mode].prompt(); + prompt(); switch ((c = readkey(stdin))) { case KEY_CTRL('C'): @@ -688,35 +715,41 @@ parseopt(const char *flag, const char **args) case 's': mode = MODE_SEARCH; searchmode = SEARCH_SUBSTR; - prompt = false; + return 0; + case 'n': + show_prompt = false; return 0; case 'a': if (!*args) die("missing -a arg"); fwdctx = strtoull(*args, &end, 10); - if (end && *end) goto badint; + if (end && *end) die("bad -a arg '%s'", *args); return 1; case 'b': if (!*args) die("missing -b arg"); bwdctx = strtoull(*args, &end, 10); - if (end && *end) goto badint; + if (end && *end) die("bad -b arg '%s'", *args); + return 1; + case 'c': + if (!*args) die("missing -c arg"); + fwdctx = bwdctx = strtoull(*args, &end, 10); + if (end && *end) die("bad -c arg '%s'", *args); 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 't': + top_prompt = true; + return 0; case 'h': - printf("%s\n", usage); + printf("Usage: tmenu [-h] [-m] [-a LINES] [-b LINES]"); exit(0); default: die("unknown opt '%s'", *flag); } return 0; - -badint: - EPRINTF("Invalid int: %s\n", *args); - exit(1); } int @@ -725,6 +758,7 @@ main(int argc, const char **argv) const char **arg; int fd; + signal(SIGWINCH, sigwinch); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0);