const std = @import("std"); const io = @import("io.zig"); /// Progress bar for long-running operations pub const ProgressBar = struct { allocator: std.mem.Allocator, total: u64, current: u64, width: usize, start_time: i64, label: []const u8, const Self = @This(); pub fn init(allocator: std.mem.Allocator, total: u64, label: []const u8) Self { return .{ .allocator = allocator, .total = total, .current = 0, .width = 40, .start_time = std.time.timestamp(), .label = label, }; } /// Update progress and redraw pub fn update(self: *Self, current: u64) void { self.current = current; self.draw(); } /// Increment progress by amount pub fn increment(self: *Self, amount: u64) void { self.current += amount; self.draw(); } /// Draw progress bar to stdout pub fn draw(self: *Self) void { const percent = if (self.total > 0) @min(100, @as(u64, self.current * 100 / self.total)) else 0; const filled = if (self.total > 0) @min(self.width, @as(usize, self.current * self.width / self.total)) else 0; const elapsed = std.time.timestamp() - self.start_time; const rate = if (elapsed > 0) self.current / @as(u64, @intCast(elapsed)) else 0; // Build bar var bar: [256]u8 = undefined; var i: usize = 0; // Clear line and move cursor to start bar[i] = '\r'; i += 1; // Add label const label_len = @min(self.label.len, 30); @memcpy(bar[i..][0..label_len], self.label[0..label_len]); i += label_len; if (label_len < 30) { bar[i] = ' '; i += 1; } // Opening bracket bar[i] = '['; i += 1; // Filled portion for (0..filled) |_| { bar[i] = '█'; i += 1; } // Empty portion for (filled..self.width) |_| { bar[i] = '░'; i += 1; } // Closing bracket bar[i] = ']'; i += 1; // Percentage const percent_str = std.fmt.bufPrint(bar[i..][0..20], " {d:>3}%", .{percent}) catch " ???%"; i += percent_str.len; // Rate if (rate > 0) { const rate_str = std.fmt.bufPrint(bar[i..][0..30], " ({d} it/s)", .{rate}) catch ""; i += rate_str.len; } // Write to stdout const stdout = std.io.getStdOut(); _ = stdout.write(bar[0..i]) catch {}; } /// Finish and print newline pub fn finish(self: *Self) void { self.current = self.total; self.draw(); const stdout = std.io.getStdOut(); _ = stdout.writeAll("\n") catch {}; } }; /// Spinner for indeterminate operations pub const Spinner = struct { frames: []const []const u8, frame_index: usize, label: []const u8, last_update: i64, const Self = @This(); pub fn init(label: []const u8) Self { return .{ .frames = &[_][]const u8{ "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, .frame_index = 0, .label = label, .last_update = std.time.timestamp(), }; } /// Draw next frame pub fn tick(self: *Self) void { const now = std.time.timestamp(); if (now - self.last_update < 0) return; // Only update every 100ms self.last_update = now; self.frame_index = (self.frame_index + 1) % self.frames.len; self.draw(); } fn draw(self: *Self) void { const frame = self.frames[self.frame_index]; const stdout = std.io.getStdOut(); var buf: [256]u8 = undefined; const msg = std.fmt.bufPrint(&buf, "\r{s} {s}", .{ frame, self.label }) catch return; _ = stdout.write(msg) catch {}; } /// Finish and clear pub fn finish(self: *Self) void { const stdout = std.io.getStdOut(); _ = stdout.writeAll("\r\x1b[K") catch {}; // Clear line _ = std.fmt.format(stdout.writer(), "✓ {s}\n", .{self.label}) catch {}; } }; /// Convenience functions pub fn showProgress(total: u64, label: []const u8) ProgressBar { return ProgressBar.init(std.heap.c_allocator, total, label); } pub fn showSpinner(label: []const u8) Spinner { return Spinner.init(label); }