authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-12 15:45:11-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-03-12 15:45:11-05:00
logf950489ed98ce8b011ef2d288e67352ab490065c
treed967cf7df8dfb51a478221e40b5e057fb8b421c2
parenta745e704f6ded6cf1ec02e9666cac554697f69ff
parentb0724a350f07c5e2e8fab572951ffaaa92860b2c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8174 from LemonBoy/progress-line-wrap

std: Better handling of line-wrapping in Progress

1 files changed, 68 insertions(+), 60 deletions(-)

lib/std/Progress.zig+68-60
......@@ -59,10 +59,6 @@ done: bool = true,
5959/// while it was still being accessed by the `refresh` function.
6060update_lock: std.Thread.Mutex = .{},
6161
62/// Keeps track of how many columns in the terminal have been output, so that
63/// we can move the cursor back later.
64columns_written: usize = undefined,
65
6662/// Represents one unit of progress. Each node can have children nodes, or
6763/// one can use integers with `update`.
6864pub const Node = struct {
......@@ -159,7 +155,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*
159155 .unprotected_estimated_total_items = estimated_total_items,
160156 .unprotected_completed_items = 0,
161157 };
162 self.columns_written = 0;
163158 self.prev_refresh_timestamp = 0;
164159 self.timer = try std.time.Timer.start();
165160 self.done = false;
......@@ -187,64 +182,65 @@ pub fn refresh(self: *Progress) void {
187182 return self.refreshWithHeldLock();
188183}
189184
185// ED -- Clear screen
186const ED = "\x1b[J";
187// DECSC -- Save cursor position
188const DECSC = "\x1b[s";
189// DECRC -- Restore cursor position
190const DECRC = "\x1b[u";
191
190192fn refreshWithHeldLock(self: *Progress) void {
191193 const is_dumb = !self.supports_ansi_escape_codes and !(std.builtin.os.tag == .windows);
192194 if (is_dumb and self.dont_print_on_dumb) return;
193195 const file = self.terminal orelse return;
194196
195 const prev_columns_written = self.columns_written;
196197 var end: usize = 0;
197 if (self.columns_written > 0) {
198 // restore the cursor position by moving the cursor
199 // `columns_written` cells to the left, then clear the rest of the
200 // line
201 if (self.supports_ansi_escape_codes) {
202 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
203 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
204 } else if (std.builtin.os.tag == .windows) winapi: {
205 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
206 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
207 unreachable;
208
209 var cursor_pos = windows.COORD{
210 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
211 .Y = info.dwCursorPosition.Y,
212 };
213
214 if (cursor_pos.X < 0)
215 cursor_pos.X = 0;
216
217 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
218
219 var written: windows.DWORD = undefined;
220 if (windows.kernel32.FillConsoleOutputAttribute(
221 file.handle,
222 info.wAttributes,
223 fill_chars,
224 cursor_pos,
225 &written,
226 ) != windows.TRUE) {
227 // Stop trying to write to this file.
228 self.terminal = null;
229 break :winapi;
230 }
231 if (windows.kernel32.FillConsoleOutputCharacterA(
232 file.handle,
233 ' ',
234 fill_chars,
235 cursor_pos,
236 &written,
237 ) != windows.TRUE) unreachable;
238
239 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
240 unreachable;
241 } else {
242 // we are in a "dumb" terminal like in acme or writing to a file
243 self.output_buffer[end] = '\n';
244 end += 1;
245 }
198 // Save the cursor position and clear the part of the screen below.
199 // Clearing only the line is not enough as the terminal may wrap the line
200 // when it becomes too long.
201 var saved_cursor_pos: windows.COORD = undefined;
202 if (self.supports_ansi_escape_codes) {
203 const seq_before = DECSC ++ ED;
204 std.mem.copy(u8, self.output_buffer[end..], seq_before);
205 end += seq_before.len;
206 } else if (std.builtin.os.tag == .windows) winapi: {
207 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
208 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
209 unreachable;
210
211 saved_cursor_pos = info.dwCursorPosition;
212
213 const window_height = @intCast(windows.DWORD, info.srWindow.Bottom - info.srWindow.Top + 1);
214 const window_width = @intCast(windows.DWORD, info.srWindow.Right - info.srWindow.Left + 1);
215 // Number of terminal cells to clear, starting from the cursor position
216 // and ending at the window bottom right corner.
217 const fill_chars = if (window_width == 0 or window_height == 0) 0 else chars: {
218 break :chars window_width * (window_height -
219 @intCast(windows.DWORD, info.dwCursorPosition.Y - info.srWindow.Top)) -
220 @intCast(windows.DWORD, info.dwCursorPosition.X - info.srWindow.Left);
221 };
246222
247 self.columns_written = 0;
223 var written: windows.DWORD = undefined;
224 if (windows.kernel32.FillConsoleOutputAttribute(
225 file.handle,
226 info.wAttributes,
227 fill_chars,
228 saved_cursor_pos,
229 &written,
230 ) != windows.TRUE) {
231 // Stop trying to write to this file.
232 self.terminal = null;
233 break :winapi;
234 }
235 if (windows.kernel32.FillConsoleOutputCharacterA(
236 file.handle,
237 ' ',
238 fill_chars,
239 saved_cursor_pos,
240 &written,
241 ) != windows.TRUE) {
242 unreachable;
243 }
248244 }
249245
250246 if (!self.done) {
......@@ -279,10 +275,26 @@ fn refreshWithHeldLock(self: *Progress) void {
279275 }
280276 }
281277
278 // We're done printing the updated message, restore the cursor position.
279 if (self.supports_ansi_escape_codes) {
280 const seq_after = DECRC;
281 std.mem.copy(u8, self.output_buffer[end..], seq_after);
282 end += seq_after.len;
283 } else if (std.builtin.os.tag != .windows) {
284 self.output_buffer[end] = '\n';
285 end += 1;
286 }
287
282288 _ = file.write(self.output_buffer[0..end]) catch |e| {
283289 // Stop trying to write to this file once it errors.
284290 self.terminal = null;
285291 };
292
293 if (std.builtin.os.tag == .windows) {
294 if (windows.kernel32.SetConsoleCursorPosition(file.handle, saved_cursor_pos) != windows.TRUE)
295 unreachable;
296 }
297
286298 self.prev_refresh_timestamp = self.timer.read();
287299}
288300
......@@ -293,17 +305,14 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
293305 self.terminal = null;
294306 return;
295307 };
296 self.columns_written = 0;
297308}
298309
299310fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
300311 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
301312 const amt = written.len;
302313 end.* += amt;
303 self.columns_written += amt;
304314 } else |err| switch (err) {
305315 error.NoSpaceLeft => {
306 self.columns_written += self.output_buffer.len - end.*;
307316 end.* = self.output_buffer.len;
308317 },
309318 }
......@@ -311,7 +320,6 @@ fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: any
311320 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
312321 if (end.* > max_end) {
313322 const suffix = "... ";
314 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
315323 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
316324 end.* = max_end + suffix.len;
317325 }