spinner.zig (4436B)
1 const std = @import("std"); 2 const ansi_term = @import("ansi-term.zig"); 3 4 pub const PredefinedSymbols = struct { 5 ///- \ | / 6 pub const default: []const u21 = &[_]u21{ '-', '\\', '|', '/' }; 7 ///⎺ ⎻ ⎼ ⎽ ⎼ ⎻ 8 pub const line: []const u21 = &[_]u21{ '⎺', '⎻', '⎼', '⎽', '⎼', '⎻' }; 9 ///◑ ◒ ◐ ◓ 10 pub const moon: []const u21 = &[_]u21{ '◑', '◒', '◐', '◓' }; 11 ///◷ ◶ ◵ ◴ 12 pub const pie: []const u21 = &[_]u21{ '◷', '◶', '◵', '◴' }; 13 ///⣾ ⣷ ⣯ ⣟ ⡿ ⢿ ⣻ ⣽ 14 pub const pixel: []const u21 = &[_]u21{ '⣾', '⣷', '⣯', '⣟', '⡿', '⢿', '⣻', '⣽' }; 15 }; 16 17 const Config = struct { 18 ///Progress spinner description. 19 description: ?[]const u8 = null, 20 symbols: []const u21 = PredefinedSymbols.default, 21 ///Clear the line when the progress spinner finishes. 22 clear_on_finish: bool = false, 23 ///Write a newline when the progress spinner finishes. 24 write_newline_on_finish: bool = true, 25 ///Character to write on completion. 26 completion_character: ?u21 = null, 27 }; 28 29 const Spinner = @This(); 30 31 writer: *std.Io.File.Writer, 32 config: Config, 33 mutex: std.Io.Mutex = std.Io.Mutex.init, 34 ///Direct access is not thread safe. Use `isFinished()` if you need thread safety. 35 finished: bool = false, 36 current_symbol_idx: usize = 0, 37 38 pub fn init(writer: *std.Io.File.Writer, config: Config) Spinner { 39 return Spinner{ 40 .writer = writer, 41 .config = config, 42 }; 43 } 44 45 fn renderComplete(self: *Spinner, completion_char: u21) !void { 46 try self.clear(); 47 var buf: [8]u8 = undefined; 48 const bytes = try std.unicode.utf8Encode(completion_char, &buf); 49 try self.writer.interface.writeAll(buf[0..bytes]); 50 51 if (self.config.description) |desc| { 52 try ansi_term.cursorForward(&self.writer.interface, 1); 53 try self.writer.interface.writeAll(desc); 54 } 55 56 try self.writer.interface.flush(); 57 } 58 59 ///Render the progress spinner and advance a visual cycle. 60 pub fn render(self: *Spinner) !void { 61 try self.mutex.lock(self.writer.io); 62 defer self.mutex.unlock(self.writer.io); 63 64 if (self.finished) return; 65 66 try self.clear(); 67 try ansi_term.hideCursor(&self.writer.interface); 68 69 var buf: [8]u8 = undefined; 70 const bytes = try std.unicode.utf8Encode(self.config.symbols[self.current_symbol_idx], &buf); 71 try self.writer.interface.writeAll(buf[0..bytes]); 72 73 if (self.config.description) |desc| { 74 try ansi_term.cursorForward(&self.writer.interface, 1); 75 try self.writer.interface.writeAll(desc); 76 } 77 78 try self.writer.interface.flush(); 79 self.current_symbol_idx = (self.current_symbol_idx + 1) % self.config.symbols.len; 80 } 81 82 ///Returns `true` if the progress spinner is finished and `false` otherwise. 83 pub fn isFinished(self: *Spinner) bool { 84 self.mutex.lockUncancelable(self.writer.io); 85 defer self.mutex.unlock(self.writer.io); 86 87 return self.finished; 88 } 89 90 ///Finish the progress spinner. 91 pub fn finish(self: *Spinner) !void { 92 try self.mutex.lock(self.writer.io); 93 defer self.mutex.unlock(self.writer.io); 94 95 self.finished = true; 96 97 if (self.config.completion_character) |char| { 98 try self.renderComplete(char); 99 } 100 101 if (self.config.clear_on_finish) try self.clear(); 102 if (self.config.write_newline_on_finish) try self.writer.interface.writeAll("\n"); 103 104 try ansi_term.showCursor(&self.writer.interface); 105 try self.writer.interface.flush(); 106 } 107 108 ///Update the description. 109 pub fn updateDescription(self: *Spinner, description: []const u8) void { 110 self.mutex.lockUncancelable(self.writer.io); 111 defer self.mutex.unlock(self.writer.io); 112 113 self.config.description = description; 114 } 115 116 ///Update the description and continue the spinner on a newline. 117 pub fn updateDescriptionNewline(self: *Spinner, description: []const u8) !void { 118 try self.mutex.lock(self.writer.io); 119 defer self.mutex.unlock(self.writer.io); 120 121 if (self.config.completion_character) |char| { 122 try self.renderComplete(char); 123 } 124 125 self.config.description = description; 126 127 try self.writer.interface.writeAll("\n"); 128 try self.writer.interface.flush(); 129 } 130 131 ///Clear the progress spinner. 132 /// 133 ///This function is not thread safe. 134 pub fn clear(self: *Spinner) !void { 135 try ansi_term.clearCurrentLine(&self.writer.interface); 136 try ansi_term.setCursorColumn(&self.writer.interface, 0); 137 try self.writer.interface.flush(); 138 }