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 };