Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
#include "bar.h"
#include "alloc.h"#include "atomic.h"#include "bfs.h"#include "bfstd.h"#include "bit.h"#include "dstring.h"#include "sighook.h"
#include <errno.h>#include <fcntl.h>#include <signal.h>#include <stdarg.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <termios.h>#include <unistd.h>
struct bfs_bar { int fd; atomic unsigned int width; atomic unsigned int height;
struct sighook *exit_hook; struct sighook *winch_hook;};
/** Get the terminal size, if possible. */static int bfs_bar_getsize(struct bfs_bar *bar) { struct winsize ws; if (xtcgetwinsize(bar->fd, &ws) != 0) { return -1; }
store(&bar->width, ws.ws_col, relaxed); store(&bar->height, ws.ws_row, relaxed); return 0;}
/** Write a string to the status bar (async-signal-safe). */static int bfs_bar_write(struct bfs_bar *bar, const char *str, size_t len) { return xwrite(bar->fd, str, len) == len ? 0 : -1;}
/** Write a string to the status bar (async-signal-safe). */static int bfs_bar_puts(struct bfs_bar *bar, const char *str) { return bfs_bar_write(bar, str, strlen(str));}
/** Number of decimal digits needed for terminal sizes. */#define ITOA_DIGITS ((USHRT_WIDTH + 2) / 3)
/** Async Signal Safe itoa(). */static char *ass_itoa(char *str, unsigned int n) { char *end = str + ITOA_DIGITS; *end = '\0';
char *c = end; do { *--c = '0' + (n % 10); n /= 10; } while (n);
size_t len = end - c; memmove(str, c, len + 1); return str + len;}
/** Reset the scrollable region and hide the bar. */static int bfs_bar_reset(struct bfs_bar *bar) { return bfs_bar_puts(bar, "\0337" // DECSC: Save cursor "\033[r" // DECSTBM: Reset scrollable region "\0338" // DECRC: Restore cursor "\033[J" // ED: Erase display from cursor to end );}
/** Hide the bar if the terminal is shorter than this. */#define BFS_BAR_MIN_HEIGHT 3
/** Update the size of the scrollable region. */static int bfs_bar_resize(struct bfs_bar *bar) { unsigned int height = load(&bar->height, relaxed); if (height < BFS_BAR_MIN_HEIGHT) { return bfs_bar_reset(bar); }
static const char PREFIX[] = "\033D" // IND: Line feed, possibly scrolling "\033[1A" // CUU: Move cursor up 1 row "\0337" // DECSC: Save cursor "\033[;"; // DECSTBM: Set scrollable region static const char SUFFIX[] = "r" // (end of DECSTBM) "\0338" // DECRC: Restore the cursor "\033[J"; // ED: Erase display from cursor to end
char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)];
// DECSTBM takes the height as the second argument char *cur = stpcpy(esc_seq, PREFIX); cur = ass_itoa(cur, height - 1); cur = stpcpy(cur, SUFFIX);
return bfs_bar_write(bar, esc_seq, cur - esc_seq);}
#ifdef SIGWINCH/** SIGWINCH handler. */static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) { struct bfs_bar *bar = arg; bfs_bar_getsize(bar); bfs_bar_resize(bar);}#endif
/** Signal handler for process-terminating signals. */static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { struct bfs_bar *bar = arg; bfs_bar_reset(bar);}
/** printf() to the status bar with a single write(). */[[_printf(2, 3)]]static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); dchar *str = dstrvprintf(format, args); va_end(args);
if (!str) { return -1; }
int ret = bfs_bar_write(bar, str, dstrlen(str)); dstrfree(str); return ret;}
struct bfs_bar *bfs_bar_show(void) { struct bfs_bar *bar = ALLOC(struct bfs_bar); if (!bar) { return NULL; }
bar->fd = open_cterm(O_RDWR | O_CLOEXEC); if (bar->fd < 0) { goto fail; }
if (bfs_bar_getsize(bar) != 0) { goto fail_close; }
bar->exit_hook = atsigexit(bfs_bar_sigexit, bar); if (!bar->exit_hook) { goto fail_close; }
#ifdef SIGWINCH bar->winch_hook = sighook(SIGWINCH, bfs_bar_sigwinch, bar, 0); if (!bar->winch_hook) { goto fail_hook; }#endif
bfs_bar_resize(bar); return bar;
fail_hook: sigunhook(bar->exit_hook);fail_close: close_quietly(bar->fd);fail: free(bar); return NULL;}
unsigned int bfs_bar_width(const struct bfs_bar *bar) { return load(&bar->width, relaxed);}
int bfs_bar_update(struct bfs_bar *bar, const char *str) { unsigned int height = load(&bar->height, relaxed); if (height < BFS_BAR_MIN_HEIGHT) { return 0; }
return bfs_bar_printf(bar, "\0337" // DECSC: Save cursor "\033[%u;0f" // HVP: Move cursor to row, column "\033[K" // EL: Erase line "\033[7m" // SGR reverse video "%s" "\033[27m" // SGR reverse video off "\0338", // DECRC: Restore cursor height, str );}
void bfs_bar_hide(struct bfs_bar *bar) { if (!bar) { return; }
sigunhook(bar->winch_hook); sigunhook(bar->exit_hook);
bfs_bar_reset(bar);
xclose(bar->fd); free(bar);}