From 77e594145c6c3da49f5e65a793d7cba90091f6bd Mon Sep 17 00:00:00 2001
From: Tavian Barnes <tavianator@tavianator.com>
Date: Tue, 3 Nov 2020 13:29:57 -0500
Subject: New -status option to display a status bar

---
 bfs.1   |   3 ++
 ctx.c   |   1 +
 ctx.h   |   2 ++
 eval.c  | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 parse.c |  19 +++++++++--
 5 files changed, 122 insertions(+), 14 deletions(-)

diff --git a/bfs.1 b/bfs.1
index 6cda4fb..0d95fd7 100644
--- a/bfs.1
+++ b/bfs.1
@@ -306,6 +306,9 @@ see
 .B \-regextype
 .IR help ).
 .TP
+.B \-status
+Display a status bar while searching.
+.TP
 .B \-unique
 Skip any files that have already been seen.
 Particularly useful along with
diff --git a/ctx.c b/ctx.c
index aafc08b..6fc6ffb 100644
--- a/ctx.c
+++ b/ctx.c
@@ -43,6 +43,7 @@ struct bfs_ctx *bfs_ctx_new(void) {
 	ctx->optlevel = 3;
 	ctx->debug = 0;
 	ctx->ignore_races = false;
+	ctx->status = false;
 	ctx->unique = false;
 	ctx->warn = false;
 	ctx->xargs_safe = false;
diff --git a/ctx.h b/ctx.h
index ba9bca4..2046ce6 100644
--- a/ctx.h
+++ b/ctx.h
@@ -75,6 +75,8 @@ struct bfs_ctx {
 	enum debug_flags debug;
 	/** Whether to ignore deletions that race with bfs (-ignore_readdir_race). */
 	bool ignore_races;
+	/** Whether to show a status bar (-status). */
+	bool status;
 	/** Whether to only return unique files (-unique). */
 	bool unique;
 	/** Whether to print warnings (-warn/-nowarn). */
diff --git a/eval.c b/eval.c
index e3806d8..09c253f 100644
--- a/eval.c
+++ b/eval.c
@@ -19,6 +19,7 @@
  */
 
 #include "eval.h"
+#include "bar.h"
 #include "bftw.h"
 #include "color.h"
 #include "darray.h"
@@ -929,17 +930,17 @@ static int eval_gettime(struct eval_state *state, struct timespec *ts) {
 }
 
 /**
- * Record the time that elapsed evaluating an expression.
+ * Record an elapsed time.
  */
-static void add_elapsed(struct expr *expr, const struct timespec *start, const struct timespec *end) {
-	expr->elapsed.tv_sec += end->tv_sec - start->tv_sec;
-	expr->elapsed.tv_nsec += end->tv_nsec - start->tv_nsec;
-	if (expr->elapsed.tv_nsec < 0) {
-		expr->elapsed.tv_nsec += 1000000000L;
-		--expr->elapsed.tv_sec;
-	} else if (expr->elapsed.tv_nsec >= 1000000000L) {
-		expr->elapsed.tv_nsec -= 1000000000L;
-		++expr->elapsed.tv_sec;
+static void timespec_elapsed(struct timespec *elapsed, const struct timespec *start, const struct timespec *end) {
+	elapsed->tv_sec += end->tv_sec - start->tv_sec;
+	elapsed->tv_nsec += end->tv_nsec - start->tv_nsec;
+	if (elapsed->tv_nsec < 0) {
+		elapsed->tv_nsec += 1000000000L;
+		--elapsed->tv_sec;
+	} else if (elapsed->tv_nsec >= 1000000000L) {
+		elapsed->tv_nsec -= 1000000000L;
+		++elapsed->tv_sec;
 	}
 }
 
@@ -961,7 +962,7 @@ static bool eval_expr(struct expr *expr, struct eval_state *state) {
 
 	if (time) {
 		if (eval_gettime(state, &end) == 0) {
-			add_elapsed(expr, &start, &end);
+			timespec_elapsed(&expr->elapsed, &start, &end);
 		}
 	}
 
@@ -1030,6 +1031,71 @@ bool eval_comma(const struct expr *expr, struct eval_state *state) {
 	return eval_expr(expr->rhs, state);
 }
 
+/** Update the status bar. */
+static void eval_status(struct eval_state *state, struct bfs_bar *bar, struct timespec *last_status, size_t count) {
+	struct timespec now;
+	if (eval_gettime(state, &now) == 0) {
+		struct timespec elapsed = {0};
+		timespec_elapsed(&elapsed, last_status, &now);
+
+		// Update every 0.1s
+		if (elapsed.tv_sec > 0 || elapsed.tv_nsec >= 100000000L) {
+			*last_status = now;
+		} else {
+			return;
+		}
+	}
+
+	size_t width = bfs_bar_width(bar);
+	if (width < 3) {
+		return;
+	}
+
+	const struct BFTW *ftwbuf = state->ftwbuf;
+
+	char *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth);
+	if (!rhs) {
+		return;
+	}
+
+	size_t rhslen = dstrlen(rhs);
+	if (3 + rhslen > width) {
+		dstresize(&rhs, 0);
+		rhslen = 0;
+	}
+
+	size_t pathmax = width - rhslen - 3;
+	size_t pathlen = ftwbuf->nameoff;
+	if (ftwbuf->depth == 0) {
+		pathlen = strlen(ftwbuf->path);
+	}
+	if (pathlen > pathmax) {
+		pathlen = pathmax;
+	}
+
+	char *status = dstrndup(ftwbuf->path, pathlen);
+	if (!status) {
+		goto out_rhs;
+	}
+	if (dstrcat(&status, "...") != 0) {
+		goto out_rhs;
+	}
+	while (dstrlen(status) < pathmax + 3) {
+		if (dstrapp(&status, ' ') != 0) {
+			goto out_rhs;
+		}
+	}
+	if (dstrcat(&status, rhs) != 0) {
+		goto out_rhs;
+	}
+
+	bfs_bar_update(bar, status);
+
+	dstrfree(status);
+out_rhs:
+	dstrfree(rhs);
+}
+
 /** Check if we've seen a file before. */
 static bool eval_file_unique(struct eval_state *state, struct trie *seen) {
 	const struct bfs_stat *statbuf = eval_stat(state);
@@ -1172,8 +1238,17 @@ static const char *dump_bftw_action(enum bftw_action action) {
 struct callback_args {
 	/** The bfs context. */
 	const struct bfs_ctx *ctx;
+
+	/** The status bar. */
+	struct bfs_bar *bar;
+	/** The time of the last status update. */
+	struct timespec last_status;
+	/** The number of files visited so far. */
+	size_t count;
+
 	/** The set of seen files. */
 	struct trie *seen;
+
 	/** Eventual return value from bfs_eval(). */
 	int ret;
 };
@@ -1183,6 +1258,7 @@ struct callback_args {
  */
 static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
 	struct callback_args *args = ptr;
+	++args->count;
 
 	const struct bfs_ctx *ctx = args->ctx;
 
@@ -1193,6 +1269,10 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
 	state.ret = &args->ret;
 	state.quit = false;
 
+	if (args->bar) {
+		eval_status(&state, args->bar, &args->last_status, args->count);
+	}
+
 	if (ftwbuf->type == BFTW_ERROR) {
 		if (!eval_should_ignore(&state, ftwbuf->error)) {
 			args->ret = EXIT_FAILURE;
@@ -1341,6 +1421,13 @@ int bfs_eval(const struct bfs_ctx *ctx) {
 		.ret = EXIT_SUCCESS,
 	};
 
+	if (ctx->status) {
+		args.bar = bfs_bar_show();
+		if (!args.bar) {
+			bfs_warning(ctx, "Couldn't show status bar: %m.\n");
+		}
+	}
+
 	struct trie seen;
 	if (ctx->unique) {
 		trie_init(&seen);
@@ -1395,5 +1482,7 @@ int bfs_eval(const struct bfs_ctx *ctx) {
 		trie_destroy(&seen);
 	}
 
+	bfs_bar_hide(args.bar);
+
 	return args.ret;
 }
diff --git a/parse.c b/parse.c
index 7113a1f..458d736 100644
--- a/parse.c
+++ b/parse.c
@@ -2305,6 +2305,14 @@ static struct expr *parse_sparse(struct parser_state *state, int arg1, int arg2)
 	return expr;
 }
 
+/**
+ * Parse -status.
+ */
+static struct expr *parse_status(struct parser_state *state, int arg1, int arg2) {
+	state->ctx->status = true;
+	return parse_nullary_option(state);
+}
+
 /**
  * Parse -x?type [bcdpflsD].
  */
@@ -2658,8 +2666,9 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) {
 	cfprintf(cout, "  ${blu}-noleaf${rs}\n");
 	cfprintf(cout, "      Ignored; for compatibility with GNU find\n");
 	cfprintf(cout, "  ${blu}-regextype${rs} ${bld}TYPE${rs}\n");
-	cfprintf(cout, "      Use ${bld}TYPE${rs}-flavored regexes (default: ${bld}posix-basic${rs}; see ${blu}-regextype${rs}"
-	                " ${bld}help${rs})\n");
+	cfprintf(cout, "      Use ${bld}TYPE${rs}-flavored regexes (default: ${bld}posix-basic${rs}; see ${blu}-regextype${rs} ${bld}help${rs})\n");
+	cfprintf(cout, "  ${blu}-status${rs}\n");
+	cfprintf(cout, "      Display a status bar while searching\n");
 	cfprintf(cout, "  ${blu}-unique${rs}\n");
 	cfprintf(cout, "      Skip any files that have already been seen\n");
 	cfprintf(cout, "  ${blu}-warn${rs}\n");
@@ -2951,10 +2960,11 @@ static const struct table_entry parse_table[] = {
 	{"-since", T_TEST, parse_since, BFS_STAT_MTIME},
 	{"-size", T_TEST, parse_size},
 	{"-sparse", T_TEST, parse_sparse},
+	{"-status", T_OPTION, parse_status},
 	{"-true", T_TEST, parse_const, true},
 	{"-type", T_TEST, parse_type, false},
 	{"-uid", T_TEST, parse_user},
-	{"-unique", T_ACTION, parse_unique},
+	{"-unique", T_OPTION, parse_unique},
 	{"-used", T_TEST, parse_used},
 	{"-user", T_TEST, parse_user},
 	{"-version", T_ACTION, parse_version},
@@ -3410,6 +3420,9 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) {
 	if (ctx->flags & BFTW_SKIP_MOUNTS) {
 		cfprintf(cerr, "${blu}-mount${rs} ");
 	}
+	if (ctx->status) {
+		cfprintf(cerr, "${blu}-status${rs} ");
+	}
 	if (ctx->unique) {
 		cfprintf(cerr, "${blu}-unique${rs} ");
 	}
-- 
cgit v1.2.3