authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-16 15:03:05-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-03-15 10:48:12-07:00
log7ebaa05bb138ea397859edf8ec96a9209482ae8e
tree405d0f5ca9f6775d276cccfb09eada21a94947e8
parent8d384722937edcfdcb3c461d316ed97405e6e2ee

std.Progress: add lock_stderr and unlock_stderr

API users can take advantage of these to freely write to the terminal which has an ongoing progress display, similar to what Ninja does when compiling C/C++ objects and a warning or error message is printed.

1 files changed, 46 insertions(+), 19 deletions(-)

lib/std/Progress.zig+46-19
......@@ -192,32 +192,28 @@ pub fn refresh(self: *Progress) void {
192192 return self.refreshWithHeldLock();
193193}
194194
195fn refreshWithHeldLock(self: *Progress) void {
196 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
197 if (is_dumb and self.dont_print_on_dumb) return;
198
199 const file = self.terminal orelse return;
200
201 var end: usize = 0;
202 if (self.columns_written > 0) {
195fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void {
196 const file = p.terminal orelse return;
197 var end = end_ptr.*;
198 if (p.columns_written > 0) {
203199 // restore the cursor position by moving the cursor
204200 // `columns_written` cells to the left, then clear the rest of the
205201 // line
206 if (self.supports_ansi_escape_codes) {
207 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
208 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
202 if (p.supports_ansi_escape_codes) {
203 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len;
204 end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
209205 } else if (builtin.os.tag == .windows) winapi: {
210 std.debug.assert(self.is_windows_terminal);
206 std.debug.assert(p.is_windows_terminal);
211207
212208 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
213209 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
214210 // stop trying to write to this file
215 self.terminal = null;
211 p.terminal = null;
216212 break :winapi;
217213 }
218214
219215 var cursor_pos = windows.COORD{
220 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
216 .X = info.dwCursorPosition.X - @intCast(windows.SHORT, p.columns_written),
221217 .Y = info.dwCursorPosition.Y,
222218 };
223219
......@@ -235,7 +231,7 @@ fn refreshWithHeldLock(self: *Progress) void {
235231 &written,
236232 ) != windows.TRUE) {
237233 // stop trying to write to this file
238 self.terminal = null;
234 p.terminal = null;
239235 break :winapi;
240236 }
241237 if (windows.kernel32.FillConsoleOutputCharacterW(
......@@ -246,22 +242,33 @@ fn refreshWithHeldLock(self: *Progress) void {
246242 &written,
247243 ) != windows.TRUE) {
248244 // stop trying to write to this file
249 self.terminal = null;
245 p.terminal = null;
250246 break :winapi;
251247 }
252248 if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) {
253249 // stop trying to write to this file
254 self.terminal = null;
250 p.terminal = null;
255251 break :winapi;
256252 }
257253 } else {
258254 // we are in a "dumb" terminal like in acme or writing to a file
259 self.output_buffer[end] = '\n';
255 p.output_buffer[end] = '\n';
260256 end += 1;
261257 }
262258
263 self.columns_written = 0;
259 p.columns_written = 0;
264260 }
261 end_ptr.* = end;
262}
263
264fn refreshWithHeldLock(self: *Progress) void {
265 const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
266 if (is_dumb and self.dont_print_on_dumb) return;
267
268 const file = self.terminal orelse return;
269
270 var end: usize = 0;
271 clearWithHeldLock(self, &end);
265272
266273 if (!self.done) {
267274 var need_ellipse = false;
......@@ -318,6 +325,26 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
318325 self.columns_written = 0;
319326}
320327
328/// Allows the caller to freely write to stderr until unlock_stderr() is called.
329/// During the lock, the progress information is cleared from the terminal.
330pub fn lock_stderr(p: *Progress) void {
331 p.update_mutex.lock();
332 if (p.terminal) |file| {
333 var end: usize = 0;
334 clearWithHeldLock(p, &end);
335 _ = file.write(p.output_buffer[0..end]) catch {
336 // stop trying to write to this file
337 p.terminal = null;
338 };
339 }
340 std.debug.getStderrMutex().lock();
341}
342
343pub fn unlock_stderr(p: *Progress) void {
344 std.debug.getStderrMutex().unlock();
345 p.update_mutex.unlock();
346}
347
321348fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
322349 if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
323350 const amt = written.len;