progress

thread safe progress bar and spinner library
git clone git://brookjeynes.dev/bjeynes/progress.git
Log | Files | Refs | README | LICENSE

multi-bar.zig (3756B)


      1 const std = @import("std");
      2 const bar = @import("bar.zig");
      3 const ansi_term = @import("ansi-term.zig");
      4 const Bar = bar.Bar;
      5 const Config = bar.Config;
      6 
      7 pub const MultiBar = struct {
      8     bars: std.ArrayList(Bar),
      9     writer: *std.Io.File.Writer,
     10     mutex: std.Io.Mutex,
     11     allocator: std.mem.Allocator,
     12     active_line: usize = 0,
     13 
     14     pub fn init(
     15         allocator: std.mem.Allocator,
     16         writer: *std.Io.File.Writer,
     17     ) MultiBar {
     18         return .{
     19             .bars = .empty,
     20             .writer = writer,
     21             .mutex = std.Io.Mutex.init,
     22             .allocator = allocator,
     23         };
     24     }
     25 
     26     pub fn deinit(self: *MultiBar) void {
     27         self.mutex.lockUncancelable(self.writer.io);
     28         defer self.mutex.unlock(self.writer.io);
     29 
     30         ansi_term.showCursor(&self.writer.interface) catch {};
     31         self.writer.interface.flush() catch {};
     32         self.bars.deinit(self.allocator);
     33     }
     34 
     35     /// Returns the minimum buffer size in bytes needed to hold `n` bars.
     36     pub fn bufSize(n: usize) usize {
     37         return n * @sizeOf(Bar) + @alignOf(Bar) - 1;
     38     }
     39 
     40     /// Add a new progress bar to the manager.
     41     /// Overrides `write_newline_on_finish` to false to ensure terminal visual integrity.
     42     /// When statically backed, `error.OutOfMemory` will be returned when the bar limit is exceeded.
     43     /// Returns the index of the newly added bar.
     44     pub fn addBar(self: *MultiBar, max_progress: usize, config: Config) !usize {
     45         var safe_config = config;
     46         safe_config.write_newline_on_finish = false;
     47 
     48         const new_pb = Bar.init(max_progress, self.writer, safe_config);
     49         const index = self.bars.items.len;
     50 
     51         try self.bars.append(self.allocator, new_pb);
     52         try self.writer.interface.writeAll("\n");
     53         self.active_line += 1;
     54         return index;
     55     }
     56 
     57     /// Returns a pointer to the bar at the given index.
     58     /// Not thread safe. The caller is responsible for synchronization
     59     /// if the bar is accessed concurrently with `render()` or other threads.
     60     pub fn bar(self: *MultiBar, index: usize) !*Bar {
     61         if (index >= self.bars.items.len) {
     62             return error.IndexOutOfBounds;
     63         }
     64         return &self.bars.items[index];
     65     }
     66 
     67     /// Render all active progress bars.
     68     /// Handles terminal cursor repositioning and dynamic reflowing if bars finish.
     69     pub fn render(self: *MultiBar) !void {
     70         self.mutex.lockUncancelable(self.writer.io);
     71         defer self.mutex.unlock(self.writer.io);
     72 
     73         if (self.active_line == 0) return;
     74 
     75         try ansi_term.cursorUp(&self.writer.interface, self.active_line);
     76 
     77         var newly_active: usize = 0;
     78         var all_finished: bool = true;
     79 
     80         for (self.bars.items) |*pb| {
     81             if (!pb.isFinished()) {
     82                 all_finished = false;
     83             }
     84 
     85             if (pb.isFinished() and pb.config.clear_on_finish) {
     86                 continue;
     87             }
     88 
     89             try pb.render();
     90             try ansi_term.cursorDown(&self.writer.interface, 1);
     91             try ansi_term.setCursorColumn(&self.writer.interface, 0);
     92 
     93             newly_active += 1;
     94         }
     95 
     96         if (newly_active < self.active_line) {
     97             const diff = self.active_line - newly_active;
     98             for (0..diff) |_| {
     99                 try ansi_term.clearCurrentLine(&self.writer.interface);
    100                 try ansi_term.cursorDown(&self.writer.interface, 1);
    101             }
    102             try ansi_term.cursorUp(&self.writer.interface, diff);
    103         }
    104 
    105         if (all_finished) {
    106             try ansi_term.showCursor(&self.writer.interface);
    107             self.active_line = 0;
    108         } else {
    109             self.active_line = newly_active;
    110         }
    111 
    112         try self.writer.interface.flush();
    113     }
    114 };