| ... | @@ -48,8 +48,9 @@ timer: ?time.Timer = null, | ... | @@ -48,8 +48,9 @@ timer: ?time.Timer = null, |
| 48 | /// Used to compare with `refresh_rate_ms`. | 48 | /// Used to compare with `refresh_rate_ms`. |
| 49 | prev_refresh_timestamp: u64 = undefined, | 49 | prev_refresh_timestamp: u64 = undefined, |
| 50 | | 50 | |
| 51 | /// This buffer represents the maximum number of bytes written to the terminal | 51 | /// This is the maximum number of bytes that can be written to the terminal each refresh. |
| 52 | /// with each refresh. | 52 | /// Anything larger than this is truncated. |
| | 53 | // we can bump this up if we need to |
| 53 | output_buffer: [256]u8 = undefined, | 54 | output_buffer: [256]u8 = undefined, |
| 54 | output_buffer_slice: []u8 = undefined, | 55 | output_buffer_slice: []u8 = undefined, |
| 55 | | 56 | |
| ... | @@ -57,6 +58,8 @@ output_buffer_slice: []u8 = undefined, | ... | @@ -57,6 +58,8 @@ output_buffer_slice: []u8 = undefined, |
| 57 | /// | 58 | /// |
| 58 | /// It is recommended to leave this as `null` so that `start` can automatically decide an | 59 | /// It is recommended to leave this as `null` so that `start` can automatically decide an |
| 59 | /// optimal width for the terminal. | 60 | /// optimal width for the terminal. |
| | 61 | /// |
| | 62 | /// Note that this will be clamped to at least 4 and output will appear malformed if it is < 4. |
| 60 | max_width: ?usize = null, | 63 | max_width: ?usize = null, |
| 61 | | 64 | |
| 62 | /// How many nanoseconds between writing updates to the terminal. | 65 | /// How many nanoseconds between writing updates to the terminal. |
| ... | @@ -156,9 +159,13 @@ pub const Node = struct { | ... | @@ -156,9 +159,13 @@ pub const Node = struct { |
| 156 | | 159 | |
| 157 | /// Create a new progress node. | 160 | /// Create a new progress node. |
| 158 | /// Call `Node.end` when done. | 161 | /// Call `Node.end` when done. |
| 159 | /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this | | |
| 160 | /// API to return Progress rather than accept it as a parameter. | | |
| 161 | /// `estimated_total_items` value of 0 means unknown. | 162 | /// `estimated_total_items` value of 0 means unknown. |
| | 163 | /// |
| | 164 | /// Note that as soon as work is started and progress output is printed, |
| | 165 | /// `std.Progress` expects you to lean back and wait and not resize the terminal. |
| | 166 | /// Resizing the terminal during progress output may result in malformed output. |
| | 167 | // TODO: solve https://github.com/ziglang/zig/issues/2765 and then change this |
| | 168 | // API to return Progress rather than accept it as a parameter. |
| 162 | pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *Node { | 169 | pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *Node { |
| 163 | const stderr = std.io.getStdErr(); | 170 | const stderr = std.io.getStdErr(); |
| 164 | self.terminal = null; | 171 | self.terminal = null; |
| ... | @@ -172,6 +179,22 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N | ... | @@ -172,6 +179,22 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N |
| 172 | // we are in a "dumb" terminal like in acme or writing to a file | 179 | // we are in a "dumb" terminal like in acme or writing to a file |
| 173 | self.terminal = stderr; | 180 | self.terminal = stderr; |
| 174 | } | 181 | } |
| | 182 | self.calculateMaxWidth(); |
| | 183 | self.root = Node{ |
| | 184 | .context = self, |
| | 185 | .parent = null, |
| | 186 | .name = name, |
| | 187 | .unprotected_estimated_total_items = estimated_total_items, |
| | 188 | .unprotected_completed_items = 0, |
| | 189 | }; |
| | 190 | self.columns_written = 0; |
| | 191 | self.prev_refresh_timestamp = 0; |
| | 192 | self.timer = time.Timer.start() catch null; |
| | 193 | self.done = false; |
| | 194 | return &self.root; |
| | 195 | } |
| | 196 | |
| | 197 | fn calculateMaxWidth(self: *Progress) void { |
| 175 | if (self.max_width == null) { | 198 | if (self.max_width == null) { |
| 176 | if (self.terminal) |terminal| { | 199 | if (self.terminal) |terminal| { |
| 177 | // choose an optimal width and account for progress output that could have been printed | 200 | // choose an optimal width and account for progress output that could have been printed |
| ... | @@ -188,18 +211,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N | ... | @@ -188,18 +211,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N |
| 188 | truncation_suffix.len, // make sure we can at least truncate | 211 | truncation_suffix.len, // make sure we can at least truncate |
| 189 | self.output_buffer.len - 1, | 212 | self.output_buffer.len - 1, |
| 190 | ); | 213 | ); |
| 191 | self.root = Node{ | | |
| 192 | .context = self, | | |
| 193 | .parent = null, | | |
| 194 | .name = name, | | |
| 195 | .unprotected_estimated_total_items = estimated_total_items, | | |
| 196 | .unprotected_completed_items = 0, | | |
| 197 | }; | | |
| 198 | self.columns_written = 0; | | |
| 199 | self.prev_refresh_timestamp = 0; | | |
| 200 | self.timer = time.Timer.start() catch null; | | |
| 201 | self.done = false; | | |
| 202 | return &self.root; | | |
| 203 | } | 214 | } |
| 204 | | 215 | |
| 205 | fn getTerminalWidth(self: Progress, file_handle: os.fd_t) !u16 { | 216 | fn getTerminalWidth(self: Progress, file_handle: os.fd_t) !u16 { |
| ... | @@ -237,7 +248,7 @@ fn getTerminalCursorColumn(self: Progress, file: std.fs.File) !u16 { | ... | @@ -237,7 +248,7 @@ fn getTerminalCursorColumn(self: Progress, file: std.fs.File) !u16 { |
| 237 | }; | 248 | }; |
| 238 | | 249 | |
| 239 | try file.writeAll("\x1b[6n"); | 250 | try file.writeAll("\x1b[6n"); |
| 240 | var buf: ["\x1b[256;256R".len]u8 = undefined; | 251 | var buf: ["\x1b[65536;65536R".len]u8 = undefined; |
| 241 | const output = try file.reader().readUntilDelimiter(&buf, 'R'); | 252 | const output = try file.reader().readUntilDelimiter(&buf, 'R'); |
| 242 | var splitter = std.mem.split(u8, output, ";"); | 253 | var splitter = std.mem.split(u8, output, ";"); |
| 243 | _ = splitter.next().?; // skip first half | 254 | _ = splitter.next().?; // skip first half |
| ... | @@ -346,7 +357,7 @@ fn refreshWithHeldLock(self: *Progress) void { | ... | @@ -346,7 +357,7 @@ fn refreshWithHeldLock(self: *Progress) void { |
| 346 | // we possibly wrote previously don't affect whether we truncate the line in `bufWrite`. | 357 | // we possibly wrote previously don't affect whether we truncate the line in `bufWrite`. |
| 347 | const unprintables = end; | 358 | const unprintables = end; |
| 348 | end = 0; | 359 | end = 0; |
| 349 | self.output_buffer_slice = self.output_buffer[unprintables .. unprintables + self.max_width.?]; | 360 | self.output_buffer_slice = self.output_buffer[unprintables..@minimum(self.output_buffer.len, unprintables + self.max_width.?)]; |
| 350 | | 361 | |
| 351 | if (!self.done) { | 362 | if (!self.done) { |
| 352 | var need_ellipsis = false; | 363 | var need_ellipsis = false; |
| ... | @@ -432,8 +443,8 @@ test "behavior on buffer overflow" { | ... | @@ -432,8 +443,8 @@ test "behavior on buffer overflow" { |
| 432 | if (skip_tests) | 443 | if (skip_tests) |
| 433 | return error.SkipZigTest; | 444 | return error.SkipZigTest; |
| 434 | | 445 | |
| 435 | // move the cursor | 446 | // uncomment this to move the cursor |
| 436 | std.debug.print("{s}", .{"A" ** 300}); | 447 | //std.debug.print("{s}", .{"A" ** 300}); |
| 437 | | 448 | |
| 438 | var progress = Progress{}; | 449 | var progress = Progress{}; |
| 439 | | 450 | |