const std = @import("std"); const colors = @import("../utils/colors.zig"); /// ProgressBar provides visual feedback for long-running operations. /// It displays progress as a percentage, item count, and throughput rate. pub const ProgressBar = struct { total: usize, current: usize, label: []const u8, start_time: i64, width: usize, /// Initialize a new progress bar pub fn init(total: usize, label: []const u8) ProgressBar { return .{ .total = total, .current = 0, .label = label, .start_time = std.time.milliTimestamp(), .width = 40, // Default bar width }; } /// Update the progress bar with current progress pub fn update(self: *ProgressBar, current: usize) void { self.current = current; self.render(); } /// Increment progress by one step pub fn increment(self: *ProgressBar) void { self.current += 1; self.render(); } /// Render the progress bar to stderr fn render(self: ProgressBar) void { const percent = if (self.total > 0) @divFloor(self.current * 100, self.total) else 0; const elapsed_ms = std.time.milliTimestamp() - self.start_time; const rate = if (elapsed_ms > 0 and self.current > 0) @as(f64, @floatFromInt(self.current)) / (@as(f64, @floatFromInt(elapsed_ms)) / 1000.0) else 0.0; // Build progress bar const filled = if (self.total > 0) @divFloor(self.current * self.width, self.total) else 0; const empty = self.width - filled; var bar_buf: [64]u8 = undefined; var bar_stream = std.io.fixedBufferStream(&bar_buf); const bar_writer = bar_stream.writer(); // Write filled portion var i: usize = 0; while (i < filled) : (i += 1) { _ = bar_writer.write("=") catch {}; } // Write empty portion i = 0; while (i < empty) : (i += 1) { _ = bar_writer.write("-") catch {}; } const bar = bar_stream.getWritten(); // Clear line and print progress const stderr = std.io.getStdErr().writer(); stderr.print("\r{s} [{s}] {d}/{d} {d}% ({d:.1} items/s)", .{ self.label, bar, self.current, self.total, percent, rate, }) catch {}; } /// Finish the progress bar and print a newline pub fn finish(self: ProgressBar) void { self.render(); const stderr = std.io.getStdErr().writer(); stderr.print("\n", .{}) catch {}; } /// Complete with a success message pub fn success(self: ProgressBar, msg: []const u8) void { self.current = self.total; self.render(); colors.printSuccess("\n{s}\n", .{msg}); } /// Get elapsed time in milliseconds pub fn elapsedMs(self: ProgressBar) i64 { return std.time.milliTimestamp() - self.start_time; } /// Get current throughput (items per second) pub fn throughput(self: ProgressBar) f64 { const elapsed_ms = self.elapsedMs(); if (elapsed_ms > 0 and self.current > 0) { return @as(f64, @floatFromInt(self.current)) / (@as(f64, @floatFromInt(elapsed_ms)) / 1000.0); } return 0.0; } }; /// Spinner provides visual feedback for indeterminate operations pub const Spinner = struct { label: []const u8, start_time: i64, frames: []const u8, frame_idx: usize, const DEFAULT_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"; pub fn init(label: []const u8) Spinner { return .{ .label = label, .start_time = std.time.milliTimestamp(), .frames = DEFAULT_FRAMES, .frame_idx = 0, }; } /// Render one frame of the spinner pub fn tick(self: *Spinner) void { const frame = self.frames[self.frame_idx % self.frames.len]; const stderr = std.io.getStdErr().writer(); stderr.print("\r{s} {c} ", .{ self.label, frame }) catch {}; self.frame_idx += 1; } /// Stop the spinner and print a newline pub fn stop(self: Spinner) void { _ = self; // Intentionally unused - for API consistency const stderr = std.io.getStdErr().writer(); stderr.print("\n", .{}) catch {}; } /// Get elapsed time in seconds pub fn elapsedSec(self: Spinner) i64 { return @divFloor(std.time.milliTimestamp() - self.start_time, 1000); } };