commit 2f1bb86c2a6a77e565b73b613725a7100c179dbf
parent 9e4b62e03223cec6611c1a4e13d29b07317e509d
Author: Brook J <jeynesbrook@gmail.com>
Date: Fri, 9 Aug 2024 16:58:46 +1000
`add()` no longer calls render
- `render()` must now be called manually.
- `render()` is now thread-safe.
- Added new config options, `write_newline_on_finish` and
`clear_on_finish`.
- Fixed issue where progress bar would finish but not restore cursor.
- Updated examples to call `render()`.
- Renamed `finish()` to `isFinished()`.
- Added private attribute `finished` to internally track a finished
state. This is helpful when dealing with threaded calls to render.
- Updated doc comments.
- Made `clear()` public. Use this when you want to manually clear the
progress bar. Note that this is different to `reset()` which resets
the bar but does not clear the line.
- Fixed issue where `set()` was not thread safe.
Diffstat:
6 files changed, 63 insertions(+), 24 deletions(-)
diff --git a/README b/README
@@ -25,6 +25,8 @@ pub fn main() !void {
while (!pb.finished()) {
try pb.add(1);
+ try pb.render();
+
std.time.sleep(std.time.ns_per_ms * 150);
}
}
diff --git a/examples/complex.zig b/examples/complex.zig
@@ -10,10 +10,14 @@ pub fn main() !void {
.description = "A complex progress bar",
.show_iterations = true,
.show_percentage = true,
+ .write_newline_on_finish = false,
});
+ defer pb.clear() catch {};
- while (!pb.finished()) {
+ while (!pb.isFinished()) {
try pb.add(1);
+ try pb.render();
+
std.time.sleep(std.time.ns_per_ms * 150);
}
}
diff --git a/examples/simple.zig b/examples/simple.zig
@@ -5,8 +5,10 @@ pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var pb = ProgressBar.init(10, stdout.any(), .{});
- while (!pb.finished()) {
+ while (!pb.isFinished()) {
try pb.add(1);
+ try pb.render();
+
std.time.sleep(std.time.ns_per_ms * 150);
}
}
diff --git a/examples/thread.zig b/examples/thread.zig
@@ -7,6 +7,8 @@ pub fn threadWorker(bar: *ProgressBar, seed: usize) !void {
for (0..5) |_| {
try bar.add(1);
+ try bar.render();
+
std.time.sleep(std.time.ns_per_ms * num);
}
}
diff --git a/src/escape_codes.zig b/src/escape_codes.zig
@@ -11,6 +11,10 @@ pub fn hideCursor(writer: anytype) !void {
try writer.writeAll(csi ++ "?25l");
}
+pub fn showCursor(writer: anytype) !void {
+ try writer.writeAll(csi ++ "?25h");
+}
+
pub fn setCursorColumn(writer: anytype, column: usize) !void {
try writer.print(csi ++ "{d}G", .{column});
}
diff --git a/src/progress.zig b/src/progress.zig
@@ -17,10 +17,14 @@ const Config = struct {
bar_suffix: u8 = '|',
/// The charater to fill the progress bar with.
bar_fill_char: u8 = '#',
- /// Whether to show the iteration count.
+ /// Show the iteration count.
show_iterations: bool = false,
- /// Whether to show the percentage.
+ /// Show the percentage.
show_percentage: bool = false,
+ /// Clear the line when the progress bar finishes.
+ clear_on_finish: bool = false,
+ /// Write a newline when the progress bar finishes.
+ write_newline_on_finish: bool = true,
};
const ProgressBar = @This();
@@ -30,6 +34,8 @@ max_progress: usize = 0,
bw: std.io.BufferedWriter(4096, std.io.AnyWriter),
config: Config,
mutex: std.Thread.Mutex = std.Thread.Mutex{},
+///Direct access is not thread safe. Use `isFinished()` if you need thread safety.
+finished: bool = false,
pub fn init(max_progress: usize, writer: std.io.AnyWriter, config: Config) ProgressBar {
return ProgressBar{
@@ -39,8 +45,7 @@ pub fn init(max_progress: usize, writer: std.io.AnyWriter, config: Config) Progr
};
}
-/// Add `num` to the progress bar. If `num` is greater than the `max_progress`,
-/// `current_progress` will be set to the max.
+///Add `num` to the progress bar. If `num` is greater than `max_progress`, `current_progress` will be set to the max.
pub fn add(self: *ProgressBar, num: usize) Error!void {
self.mutex.lock();
defer self.mutex.unlock();
@@ -54,14 +59,9 @@ pub fn add(self: *ProgressBar, num: usize) Error!void {
} else {
self.current_progress = self.max_progress;
}
-
- self.render() catch return Error.FailedToRender;
}
-/// Render the progress bar. The progress bar must have a `max_progress` greater
-/// than 0.
-///
-/// Render does not need to be explicitly called as `add` will render the bar.
+///Render the progress bar. The progress bar must have a `max_progress` greater than 0.
pub fn render(self: *ProgressBar) !void {
if (self.max_progress == 0) {
return Error.MaxIsZero;
@@ -69,7 +69,12 @@ pub fn render(self: *ProgressBar) !void {
const winsize = try termsize.termSize(std.io.getStdOut());
- try self.clearLine();
+ self.mutex.lock();
+ defer self.mutex.unlock();
+
+ if (self.finished) return;
+
+ try self.clear();
const percentage = @as(f32, @floatFromInt(self.current_progress)) / @as(f32, @floatFromInt(self.max_progress));
@@ -122,45 +127,61 @@ pub fn render(self: *ProgressBar) !void {
_ = try self.bw.writer().writeByte(self.config.bar_suffix);
- if (self.current_progress >= self.max_progress) {
- _ = try self.bw.write("\n");
+ if (self.current_progress >= self.max_progress and !self.finished) {
+ self.finished = true;
+
+ if (self.config.clear_on_finish) try self.clear();
+ if (self.config.write_newline_on_finish) _ = try self.bw.write("\n");
+ try escape_codes.showCursor(self.bw.writer());
}
try self.bw.flush();
}
-/// Returns `true` if the progress bar is finished and `false` otherwise.
-pub fn finished(self: *ProgressBar) bool {
+///Returns `true` if the progress bar is finished and `false` otherwise.
+pub fn isFinished(self: *ProgressBar) bool {
self.mutex.lock();
defer self.mutex.unlock();
- return self.current_progress >= self.max_progress;
+ return self.current_progress >= self.max_progress or self.finished;
}
-/// Finish the progress bar.
+///Finish the progress bar.
pub fn finish(self: *ProgressBar) void {
self.mutex.lock();
defer self.mutex.unlock();
+ self.finished = true;
self.current_progress = self.max_progress;
+
+ try escape_codes.showCursor(self.bw.writer());
+ try self.bw.flush();
}
-/// Reset the progress bar.
+///Reset the progress bar.
pub fn reset(self: *ProgressBar) void {
self.mutex.lock();
defer self.mutex.unlock();
+ self.finished = false;
self.current_progress = 0;
}
-/// Set the progress bar to the `num`. If `num` is greater than the `max_progress`,
-/// a `NumGreaterThanMax` error will be returned.
+///Set the progress bar to `num`.
+///
+///If `num` is equal to `max_progress`, `finished` is set true.
+///If `num` is greater than `max_progress`, `Error.NumGreaterThanMax` is returned.
pub fn set(self: *ProgressBar, num: usize) Error!void {
+ self.mutex.lock();
+ defer self.mutex.unlock();
+
+ if (num == self.max_progress) self.finished = true;
if (num > self.max_progress) return Error.NumGreaterThanMax;
+
self.current_progress = num;
}
-/// Update the description.
+///Update the description.
pub fn update_description(self: *ProgressBar, description: []const u8) void {
self.mutex.lock();
defer self.mutex.unlock();
@@ -168,7 +189,11 @@ pub fn update_description(self: *ProgressBar, description: []const u8) void {
self.config.description = description;
}
-fn clearLine(self: *ProgressBar) !void {
+///Clear the progress bar.
+///
+///This function is not thread safe.
+pub fn clear(self: *ProgressBar) !void {
try escape_codes.clearCurrentLine(self.bw.writer());
try escape_codes.setCursorColumn(self.bw.writer(), 0);
+ try self.bw.flush();
}