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,...@@ -59,10 +59,6 @@ done: bool = true,
59/// while it was still being accessed by the `refresh` function.59/// while it was still being accessed by the `refresh` function.
60update_lock: std.Thread.Mutex = .{},60update_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
66/// Represents one unit of progress. Each node can have children nodes, or62/// Represents one unit of progress. Each node can have children nodes, or
67/// one can use integers with `update`.63/// one can use integers with `update`.
68pub const Node = struct {64pub const Node = struct {
...@@ -159,7 +155,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*...@@ -159,7 +155,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*
159 .unprotected_estimated_total_items = estimated_total_items,155 .unprotected_estimated_total_items = estimated_total_items,
160 .unprotected_completed_items = 0,156 .unprotected_completed_items = 0,
161 };157 };
162 self.columns_written = 0;
163 self.prev_refresh_timestamp = 0;158 self.prev_refresh_timestamp = 0;
164 self.timer = try std.time.Timer.start();159 self.timer = try std.time.Timer.start();
165 self.done = false;160 self.done = false;
...@@ -187,64 +182,65 @@ pub fn refresh(self: *Progress) void {...@@ -187,64 +182,65 @@ pub fn refresh(self: *Progress) void {
187 return self.refreshWithHeldLock();182 return self.refreshWithHeldLock();
188}183}
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
190fn refreshWithHeldLock(self: *Progress) void {192fn refreshWithHeldLock(self: *Progress) void {
191 const is_dumb = !self.supports_ansi_escape_codes and !(std.builtin.os.tag == .windows);193 const is_dumb = !self.supports_ansi_escape_codes and !(std.builtin.os.tag == .windows);
192 if (is_dumb and self.dont_print_on_dumb) return;194 if (is_dumb and self.dont_print_on_dumb) return;
193 const file = self.terminal orelse return;195 const file = self.terminal orelse return;
194196
195 const prev_columns_written = self.columns_written;
196 var end: usize = 0;197 var end: usize = 0;
197 if (self.columns_written > 0) {198 // Save the cursor position and clear the part of the screen below.
198 // restore the cursor position by moving the cursor199 // Clearing only the line is not enough as the terminal may wrap the line
199 // `columns_written` cells to the left, then clear the rest of the200 // when it becomes too long.
200 // line201 var saved_cursor_pos: windows.COORD = undefined;
201 if (self.supports_ansi_escape_codes) {202 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 const seq_before = DECSC ++ ED;
203 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;204 std.mem.copy(u8, self.output_buffer[end..], seq_before);
204 } else if (std.builtin.os.tag == .windows) winapi: {205 end += seq_before.len;
205 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;206 } else if (std.builtin.os.tag == .windows) winapi: {
206 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)207 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
207 unreachable;208 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
208209 unreachable;
209 var cursor_pos = windows.COORD{210
210 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),211 saved_cursor_pos = info.dwCursorPosition;
211 .Y = info.dwCursorPosition.Y,212
212 };213 const window_height = @intCast(windows.DWORD, info.srWindow.Bottom - info.srWindow.Top + 1);
213214 const window_width = @intCast(windows.DWORD, info.srWindow.Right - info.srWindow.Left + 1);
214 if (cursor_pos.X < 0)215 // Number of terminal cells to clear, starting from the cursor position
215 cursor_pos.X = 0;216 // and ending at the window bottom right corner.
216217 const fill_chars = if (window_width == 0 or window_height == 0) 0 else chars: {
217 const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);218 break :chars window_width * (window_height -
218219 @intCast(windows.DWORD, info.dwCursorPosition.Y - info.srWindow.Top)) -
219 var written: windows.DWORD = undefined;220 @intCast(windows.DWORD, info.dwCursorPosition.X - info.srWindow.Left);
220 if (windows.kernel32.FillConsoleOutputAttribute(221 };
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 }
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 }
248 }244 }
249245
250 if (!self.done) {246 if (!self.done) {
...@@ -279,10 +275,26 @@ fn refreshWithHeldLock(self: *Progress) void {...@@ -279,10 +275,26 @@ fn refreshWithHeldLock(self: *Progress) void {
279 }275 }
280 }276 }
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
282 _ = file.write(self.output_buffer[0..end]) catch |e| {288 _ = file.write(self.output_buffer[0..end]) catch |e| {
283 // Stop trying to write to this file once it errors.289 // Stop trying to write to this file once it errors.
284 self.terminal = null;290 self.terminal = null;
285 };291 };
292
293 if (std.builtin.os.tag == .windows) {
294 if (windows.kernel32.SetConsoleCursorPosition(file.handle, saved_cursor_pos) != windows.TRUE)
295 unreachable;
296 }
297
286 self.prev_refresh_timestamp = self.timer.read();298 self.prev_refresh_timestamp = self.timer.read();
287}299}
288300
...@@ -293,17 +305,14 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {...@@ -293,17 +305,14 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
293 self.terminal = null;305 self.terminal = null;
294 return;306 return;
295 };307 };
296 self.columns_written = 0;
297}308}
298309
299fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {310fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
300 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {311 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
301 const amt = written.len;312 const amt = written.len;
302 end.* += amt;313 end.* += amt;
303 self.columns_written += amt;
304 } else |err| switch (err) {314 } else |err| switch (err) {
305 error.NoSpaceLeft => {315 error.NoSpaceLeft => {
306 self.columns_written += self.output_buffer.len - end.*;
307 end.* = self.output_buffer.len;316 end.* = self.output_buffer.len;
308 },317 },
309 }318 }
...@@ -311,7 +320,6 @@ fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: any...@@ -311,7 +320,6 @@ fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: any
311 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;320 const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
312 if (end.* > max_end) {321 if (end.* > max_end) {
313 const suffix = "... ";322 const suffix = "... ";
314 self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
315 std.mem.copy(u8, self.output_buffer[max_end..], suffix);323 std.mem.copy(u8, self.output_buffer[max_end..], suffix);
316 end.* = max_end + suffix.len;324 end.* = max_end + suffix.len;
317 }325 }