tmenu

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

commit 10159ed5d2665304cd03cd9b657c7df41296a2cc
parent 8899735b8c7153365255276a2e7666a9c2074afd
Author: Louis Burda <quent.burda@gmail.com>
Date:   Mon, 19 Jul 2021 23:14:26 +0200

Added search mode and replaced fgets

Diffstat:
Mtmenu.c | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
1 file changed, 180 insertions(+), 45 deletions(-)

diff --git a/tmenu.c b/tmenu.c @@ -5,14 +5,15 @@ #include <unistd.h> #include <termios.h> +#include <sys/ioctl.h> -void* checkp(void *p); -void die(const char *fmtstr, ...); -char* aprintf(const char *fmtstr, ...); -void run(); -int handleopt(const char *flag, const char **args); +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) -const char *userfile = NULL; +enum { + MODE_BROWSE, + MODE_SEARCH, +}; enum { CODE_UP = 0x100, @@ -21,6 +22,36 @@ enum { CODE_RIGHT = 0x103, }; +void* checkp(void *p); +void die(const char *fmtstr, ...); +char* aprintf(const char *fmtstr, ...); +ssize_t fgetln(char *buf, size_t size, FILE *f); + +int handleopt(const char *flag, const char **args); + +void search_handlekey(int c); +void search_selnext(); + +const char *CSI_CLEARLINE = "\x1b[K"; +const char *CSI_HIDECUR = "\x1b[25l"; +const char *CSI_SHOWCUR = "\x1b[25h"; + +const char *prompts[] = { + [MODE_BROWSE] = "(browse) ", + [MODE_SEARCH] = "(search) ", +}; + +const char *userfile = NULL; + +FILE *f; + +size_t linesel, linecap, linec, *lines = NULL; + +char searchbuf[1024] = { 0 }; +size_t searchc = 0; + +int mode = MODE_BROWSE; + void* checkp(void *p) { @@ -84,28 +115,90 @@ readcode(FILE *f) } void -addent(size_t linec, size_t pos, size_t **lines, size_t *linecap) +setent(size_t linei, size_t pos) { - (*lines)[linec] = pos; - if (linec == *linecap - 1) { - *linecap *= 2; - *lines = checkp(realloc(*lines, *linecap)); + if (linei >= linecap) { + linecap *= 2; + lines = checkp(realloc(lines, linecap)); } + lines[linei] = pos; +} + +void +search_handlekey(int c) +{ + switch (c) { + case 127: /* DEL */ + if (searchc) searchc--; + return; + default: + if (searchc < sizeof(searchbuf)) + searchbuf[searchc++] = c & 0xff; + } + search_selnext(); +} + +size_t +linesize(size_t linei) +{ + return lines[linei + 1] - lines[linei]; +} + +char* +readline(char *buf, int linei) +{ + size_t nleft, nread; + char *bp, *tok; + + fseek(f, lines[linei], SEEK_SET); + nleft = linesize(linei); + buf = bp = checkp(realloc(buf, nleft)); + while (nleft && (nread = fread(bp, 1, nleft, f)) > 0) { + nleft -= nread; + bp += nread; + } + if (nread < 0) die("Failed to read line %i from input\n", linei); + tok = memchr(buf, '\n', linesize(linei)); + if (tok) *tok = '\0'; + + return buf; +} + +void +search_selnext() +{ + size_t i, linei, nread, nleft; + char *line = NULL, *end, *bp; + + if (!searchc) return; + + for (i = 0; i < linec; i++) { + linei = (linesel + 1 + i) % linec; + line = bp = readline(line, linei); + end = line + linesize(linei); + while (end - bp && (bp = memchr(bp, searchbuf[0], end - bp))) { + if (!memcmp(bp, searchbuf, MIN(end - bp, searchc))) { + linesel = linei; + goto exit; + } + bp += 1; + } + } + +exit: + free(line); } void run() { - char *tok, buf[1024]; struct termios prevterm, newterm; - size_t pos, start, linesel, nread, - linec, linecap, *lines = NULL; - FILE *f; + ssize_t pos, start, nread; + const char *prompt; + struct winsize ws = { 0 }; + char *tok, iobuf[1024], *line = NULL; int c, termw; - if (tcgetattr(STDIN_FILENO, &prevterm)) - die("Failed to get terminal properies\n"); - linecap = 100; lines = checkp(calloc(linecap, sizeof(size_t))); @@ -114,60 +207,83 @@ run() die("Failed to create temporary file\n"); linec = start = pos = 0; - while ((fgets(buf, sizeof(buf), stdin))) { - if ((tok = memchr(buf, '\n', sizeof(buf)))) - addent(linec++, start, &lines, &linecap); - nread = tok ? tok - buf + 1 : sizeof(buf); - if (fwrite(buf, 1, nread, f) != nread) - die("Writing to tmp file failed\n"); + while ((nread = fgetln(iobuf, sizeof(iobuf), stdin)) > 0) { pos += nread; - if (tok) start = pos; + if (fwrite(iobuf, 1, nread, f) != nread) + die("Writing to tmp file failed\n"); + if (iobuf[nread-1] == '\n') { + setent(linec++, start); + start = pos; + } } + setent(linec, pos); fseek(f, 0, SEEK_SET); + + if (!freopen("/dev/tty", "r", stdin)) + die("Failed to reattach to pseudo tty\n"); } else { if (!(f = fopen(userfile, "r"))) die("Failed to open file for reading: %s\n", userfile); linec = start = pos = 0; - while ((fgets(buf, sizeof(buf), f))) { - if ((tok = memchr(buf, '\n', sizeof(buf)))) - addent(linec++, start, &lines, &linecap); - pos += tok ? tok - buf + 1 : sizeof(buf) - 1; - if (tok) start = pos; + while ((nread = fgetln(iobuf, sizeof(iobuf), f)) > 0) { + pos += nread; + if (iobuf[nread-1] == '\n') { + setent(linec++, start); + start = pos; + } } + setent(linec, pos); fseek(f, 0, SEEK_SET); } if (!linec) return; - printf("LINES: %li\n", linec); + + if (tcgetattr(fileno(stdin), &prevterm)) + die("Failed to get terminal properies\n"); cfmakeraw(&newterm); - if (tcsetattr(STDIN_FILENO, TCSANOW, &newterm)) + if (tcsetattr(fileno(stdin), TCSANOW, &newterm)) die("Failed to set new terminal properties\n"); + termw = (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) ? ws.ws_col : 80; + + fprintf(stderr, "%s", CSI_HIDECUR); + linesel = 0; do { - /* TODO: render lines */ - termw = 80; - fseek(f, lines[linesel], SEEK_SET); - printf("%li %li\n\r", linesel, lines[linesel]); - fgets(buf, termw, f); - if (buf[termw-1] == '\n') buf[termw-1] = '\0'; - printf("%s\n\r", buf); + if (mode == MODE_BROWSE) { + line = readline(line, linesel); + fprintf(stderr, "%s%s%.*s\r", CSI_CLEARLINE, + prompts[mode], termw - (int) strlen(prompts[mode]), + line); + } else if (mode == MODE_SEARCH) { + line = readline(line, linesel); + fprintf(stderr, "%s%s%.*s: %.*s\r", CSI_CLEARLINE, + prompts[mode], (int) searchc, searchbuf, + (int) (termw - strlen(prompts[mode]) - searchc), + line); + } c = getc(stdin); if (c == 0x1b) c = readcode(stdin); - printf("CODE: %i\n\r", c); switch (c) { case 0x03: /* CTRL+C */ goto exit; + case 0x13: /* CTRL+S */ + if (mode != MODE_SEARCH) { + mode = MODE_SEARCH; + search_selnext(); + } + break; + case 0x02: /* CTRL+B */ + mode = MODE_BROWSE; + break; case '\r': - fseek(f, lines[linesel], SEEK_SET); - while ((fgets(buf, sizeof(buf), f)) - && buf[sizeof(buf)-1] != '\n') - printf("%s", buf); + line = readline(line, linesel); + printf("%.*s", (int) linesize(linesel), line); goto exit; case CODE_UP: if (linesel != 0) linesel--; @@ -175,15 +291,33 @@ run() case CODE_DOWN: if (linesel != linec - 1) linesel++; break; + default: + if (mode == MODE_SEARCH) { + search_handlekey(c); + } } } while (c >= 0); exit: - tcsetattr(STDIN_FILENO, TCSANOW, &prevterm); + fprintf(stderr, "\r%s", CSI_CLEARLINE); + fprintf(stderr, "\r%s", CSI_SHOWCUR); + + tcsetattr(fileno(stdin), TCSANOW, &prevterm); fclose(f); } +ssize_t +fgetln(char *buf, size_t size, FILE *f) +{ + size_t i = 0; + + for (i = 0; i < size && (buf[i] = fgetc(f)) >= 0; i++) + if (buf[i] == '\n') return i + 1; + + return (buf[i] < 0) ? -1 : i; +} + int handleopt(const char *flag, const char **args) { @@ -209,3 +343,4 @@ main(int argc, const char **argv) run(); } +