iomenu

interactive text selection menu
Log | Files | Refs | README | LICENSE

commit 1a73e9911476f01c8a4548f020b8def4e62bfe2b
parent 00bb578f57d039ac6e6c2ce2835da575c78c76f3
Author: Josuah Demangeon <mail@josuah.net>
Date:   Thu, 18 Jan 2018 23:43:01 +0100

use utf8_col() instead of format() which used a buffer, remove -p flag

Diffstat:
Miomenu.1 | 51+++++++++++++++++++++++++++++++++++++++++++++------
Miomenu.c | 149++++++++++++++++++++++++++++++-------------------------------------------------
Mtest.c | 6+++---
Mutf8.c | 4++--
4 files changed, 107 insertions(+), 103 deletions(-)

diff --git a/iomenu.1 b/iomenu.1 @@ -1,81 +1,120 @@ .Dd aug 21, 2017 .Dt IOMENU 1 .Os +. +. .Sh NAME +. .Nm iomenu .Nd interactive selection menu +. +. .Sh SYNOPSIS +. .Nm .Op Fl # -.Op Fl p Ar prompt +. +. .Sh DESCRIPTION +. .Nm is an interactive filtering and selection tool for the terminal. +. .Pp It reads lines from standard input, and prompt for a selection. The selected line is printed to standard output. -.Bl -tag -width XXXXXXXXXXXXXXXX -.It Fl p Ar prompt -Set the prompt to display at the beginning of the input to -.Ar prompt . +. +.Bl -tag -width 6n +. .It Fl # If a line starts with .Li # , .Nm will interprete it as a header, which always matches, and can not be printed. -.Pp +. +. .Sh KEY BINDINGS +. An active selection is highlighted, and can be controlled with keybindings. As printable keys are entered, the lines are filtered to match each word from the input. +. .Bl -tag -width XXXXXXXXXXXXXXX +. .It Ic Up Ns , Ic Down Ns , Ic Ctrl + p Ns , Ic Ctrl + n Move selection to the previous/next item. +. .It Ic PageUp Ns , Ic PageDown Ns , Ic Alt + v Ns , Ic Ctrl + v Move one page up or down. +. .It Ic Ctrl + m Ns , Ic Ctrl + j Ns , Ic Enter Print the selection to the standard output, and exit 0. +. .It Ic Ctrl + h Ns , Ic Bakcspace Remove last character from current input. +. .It Ic Ctrl + w Remove last word from current input. +. .It Ic Ctrl + u Remove the whole input string. +. .It Ic Ctrl + i Ns , Ic Tab Fill the input with current selection. +. .El +. +. .Sh EXIT STATUS +. .Ex -std +. +. .Sh EXAMPLES +. Go to a subdirectory: +. .Bd -literal -offset XX cd "$(find . -type d | iomenu)" .Ed +. .Pp Edit a file located in .Ev HOME : +. .Bd -literal -offset XX $EDITOR "$(find "$HOME" -type f | iomenu)" .Ed +. .Pp Play an audio file: +. .Bd -literal -offset XX mplayer "$(find ~/Music | iomenu)" .Ed +. .Pp Select a background job to attach to: +. .Bd -literal -offset XX fg "%$(jobs | iomenu | cut -c 2)" .Ed +. .Pp Filter "ps" output and print a process ID .Bd -literal -offset XX { printf '#'; ps ax; } | iomenu -# | sed -r 's/ *([0-9]*).*/\1/' .Ed +. +. .Sh SEE ALSO +. .Xr dmenu 1 , .Xr slmenu 1 , .Xr vis-menu 1 +. +. .Sh AUTORS +. .An Josuah Demangeon Aq Mt mail@josuah.net diff --git a/iomenu.c b/iomenu.c @@ -31,7 +31,6 @@ char **linev = NULL, **matchv = NULL; char input[LINE_MAX], formatted[LINE_MAX * 8]; int flag_hs = 0; -char *flag_p = ""; static char * io_strstr(const char *str1, const char *str2) @@ -39,7 +38,7 @@ io_strstr(const char *str1, const char *str2) const char *s1; const char *s2; - while (1) { + for (;;) { s1 = str1; s2 = str2; while (*s1 != '\0' && tolower(*s1) == tolower(*s2)) @@ -73,7 +72,7 @@ match_line(char *line, char **tokv) * Free the structures, reset the terminal state and exit with an error message. */ static void -die(const char *s) +err(const char *s) { tcsetattr(ttyfd, TCSANOW, &termios); close(ttyfd); @@ -96,10 +95,10 @@ split_lines(char *buf) linec = 1; for (b = buf; (b = strchr(b, '\n')) != NULL && b[1] != '\0'; b++) linec++; - if ((lv = linev = calloc(linec + 1, sizeof (char **))) == NULL) - die("calloc"); - if ((mv = matchv = calloc(linec + 1, sizeof (char **))) == NULL) - die("calloc"); + if ((lv = linev = calloc(linec + 1, sizeof(char **))) == NULL) + err("calloc"); + if ((mv = matchv = calloc(linec + 1, sizeof(char **))) == NULL) + err("calloc"); *mv = *lv = b = buf; while ((b = strchr(b, '\n')) != NULL) { *b = '\0'; @@ -120,13 +119,13 @@ read_stdin(void) size = BUFSIZ; off = 0; if ((buf = malloc(size)) == NULL) - die("malloc"); + err("malloc"); while ((len = read(STDIN_FILENO, buf + off, size - off)) > 0) { off += len; if (off == size) { size *= 2; if ((buf = realloc(buf, size + 1)) == NULL) - die("realloc"); + err("realloc"); } } buf[off] = '\0'; @@ -134,14 +133,14 @@ read_stdin(void) } static void -move(signed int sign) +move(int direction) { - extern char **matchv; - extern int matchc; + extern char **matchv; + extern int matchc; int i; - for (i = cur + sign; 0 <= i && i < matchc; i += sign) { + for (i = cur + direction; 0 <= i && i < matchc; i += direction) { if (!flag_hs || matchv[i][0] != '#') { cur = i; break; @@ -171,7 +170,7 @@ filter(int searchc, char **searchv) extern int matchc, cur; int n; - char *tokv[sizeof(input) / 2 * sizeof(char *) + sizeof(NULL)]; + char *tokv[sizeof(input) * sizeof(char *) + sizeof(NULL)]; char *s, buf[sizeof(input)]; strncpy(buf, input, sizeof(input)); @@ -329,71 +328,35 @@ top: return 1; } -static char * -format(char *str, int col) -{ - extern struct winsize ws; - extern char formatted[LINE_MAX * 8]; - - int c, n, w; - long rune = 0; - char *fmt; - - fmt = formatted; - for (c = 0; *str != '\0' && c < col; ) { - if (*str == '\t') { - int t = 8 - c % 8; - while (t-- && c < col) { - *fmt++ = ' '; - c++; - } - str++; - } else if ((n = utf8_torune(&rune, str)) > 0 && - (w = utf8_wcwidth(rune)) > 0) { - while (n--) - *fmt++ = *str++; - c += w; - } else { - *fmt++ = '?'; - str += n; - c ++; - } - } - *fmt = '\0'; - - return formatted; -} - static void print_line(char *line, int highlight) { extern struct winsize ws; - if (flag_hs && line[0] == '#') { - format(line + 1, ws.ws_col - 1); - fprintf(stderr, "\n\x1b[1m %s\x1b[m", formatted); - } else if (highlight) { - format(line, ws.ws_col - 1); - fprintf(stderr, "\n\x1b[47;30m\x1b[K %s\x1b[m", formatted); - } else { - format(line, ws.ws_col - 1); - fprintf(stderr, "\n %s", formatted); - } + if (flag_hs && line[0] == '#') + fprintf(stderr, "\n\x1b[1m\r%.*s\x1b[m", + utf8_col(line + 1, ws.ws_col, 0), line + 1); + else if (highlight) + fprintf(stderr, "\n\x1b[47;30m\x1b[K\r%.*s\x1b[m", + utf8_col(line, ws.ws_col, 0), line); + else + fprintf(stderr, "\n%.*s", + utf8_col(line, ws.ws_col, 0), line); } static void print_screen(void) { extern struct winsize ws; - extern char **matchv, *flag_p, input[LINE_MAX]; + extern char **matchv, input[LINE_MAX]; extern int matchc; char **m; - int p, i, cols, rows; + int p, i, c, cols, rows; - cols = ws.ws_col - 1; - rows = ws.ws_row - 1; - p = 0; + cols = ws.ws_col; + rows = ws.ws_row - 1; /* keep one line for user input */ + p = c = 0; i = cur - cur % rows; m = matchv + i; fputs("\x1b[2J", stderr); @@ -402,13 +365,7 @@ print_screen(void) p++, i++, m++; } fputs("\x1b[H", stderr); - if (*flag_p) { - format(flag_p, cols - 2); - fprintf(stderr, "\x1b[30;47m %s \x1b[m", formatted); - cols -= strlen(formatted) + 2; - } - fputc(' ', stderr); - fputs(format(input, cols), stderr); + fprintf(stderr, "%.*s", utf8_col(input, cols, c), input); fflush(stderr); } @@ -448,7 +405,7 @@ sigwinch() extern struct winsize ws; if (ioctl(ttyfd, TIOCGWINSZ, &ws) < 0) - die("ioctl"); + err("ioctl"); print_screen(); signal(SIGWINCH, sigwinch); } @@ -456,7 +413,7 @@ sigwinch() static void usage(void) { - fputs("usage: iomenu [-#] [-p flag_p]\n", stderr); + fputs("usage: iomenu [-#]\n", stderr); exit(EXIT_FAILURE); } @@ -466,17 +423,10 @@ usage(void) static void parse_opt(int argc, char *argv[]) { - extern char *flag_p; - for (argv++, argc--; argc > 0; argv++, argc--) { if (argv[0][0] != '-') usage(); switch ((*argv)[1]) { - case 'p': - if (!--argc) - usage(); - flag_p = *++argv; - break; case '#': flag_hs = 1; break; @@ -486,6 +436,26 @@ parse_opt(int argc, char *argv[]) } } +void +init(void) +{ + extern char input[LINE_MAX]; + + input[0] = '\0'; + read_stdin(); + filter(linec, linev); + + if (freopen("/dev/tty", "r", stdin) == NULL) + err("freopen /dev/tty"); + if (freopen("/dev/tty", "w", stderr) == NULL) + err("freopen /dev/tty"); + if ((ttyfd = open("/dev/tty", O_RDWR)) < 0) + err("open /dev/tty"); + + set_terminal(); + sigwinch(); +} + /* * Read stdin in a buffer, filling a table of lines, then re-open stdin to * /dev/tty for an interactive (raw) session to let the user filter and select @@ -494,23 +464,18 @@ parse_opt(int argc, char *argv[]) int main(int argc, char *argv[]) { - extern char input[LINE_MAX]; - int exit_code; parse_opt(argc, argv); - read_stdin(); - filter(linec, linev); - if (!freopen("/dev/tty", "r", stdin)) - die("freopen /dev/tty"); - if (!freopen("/dev/tty", "w", stderr)) - die("freopen /dev/tty"); - ttyfd = open("/dev/tty", O_RDWR); - set_terminal(); - sigwinch(); - input[0] = '\0'; + init(); + +#ifdef __OpenBSD__ + pledge("stdio tty", NULL); +#endif + while ((exit_code = key(fgetc(stdin))) > 0) print_screen(); + print_screen(); reset_terminal(); close(ttyfd); diff --git a/test.c b/test.c @@ -6,10 +6,10 @@ int main(void) { int c, col, o, off; - char s[] = " 浪漫的夢想"; + char s[] = "\t\t浪漫的夢想"; - for (off = 0; off < 10; off++) { - for (col = off + 1; col < 25; col++) { + for (off = 0; off < 15; off++) { + for (col = off + 1; col < 30; col++) { for (c = 0; c < col; c++) putchar(c % 8 == 0 ? '>' : '_'); printf(" %d\n", col); diff --git a/utf8.c b/utf8.c @@ -173,14 +173,14 @@ utf8_col(char *str, int col, int off) long rune; char *pos, *s; - for (s = str; off < col;) { + for (s = str; off <= col;) { pos = s; if (*s == '\0') break; s += utf8_torune(&rune, s); if (rune == '\t') - off += 7 - (off) % 8; + off += 8 - (off % 8); else off += utf8_wcwidth(rune); }