diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 0b9a060fb573..9e08932bbb5f 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -129,6 +129,7 @@ fn mainTerminal() void { var fail_count: usize = 0; var progress = std.Progress{ .dont_print_on_dumb = true, + .emulate_one_line_bar = true, }; const root_node = progress.start("Test", test_fn_list.len); const have_tty = progress.terminal != null and diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 19f90e86a91c..2039f3e8f2f1 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -13,8 +13,11 @@ const testing = std.testing; const assert = std.debug.assert; const Progress = @This(); +const output_buffer_rows = 20; +const output_buffer_cols = 200; + /// `null` if the current node (and its children) should -/// not print on update() +/// not print on update(), refreshes the `output_buffer` nonetheless terminal: ?std.fs.File = undefined, /// Is this a windows API terminal (note: this is not the same as being run on windows @@ -39,11 +42,14 @@ timer: ?std.time.Timer = null, /// When the previous refresh was written to the terminal. /// Used to compare with `refresh_rate_ms`. -prev_refresh_timestamp: u64 = undefined, +prev_refresh_timestamp: u64 = 0, + +/// This buffer represents the maximum number of rows and columns +/// written to the terminal with each refresh. +output_buffer: [output_buffer_rows][output_buffer_cols]u8 = undefined, -/// This buffer represents the maximum number of bytes written to the terminal -/// with each refresh. -output_buffer: [100]u8 = undefined, +/// This symbol will be used as a bullet in the tree listing. +bullet: u8 = '-', /// How many nanoseconds between writing updates to the terminal. refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, @@ -53,14 +59,27 @@ initial_delay_ns: u64 = 500 * std.time.ns_per_ms, done: bool = true, -/// Protects the `refresh` function, as well as `node.recently_updated_child`. +/// Protects the `refresh` function, as well as `Node` attributes. /// Without this, callsites would call `Node.end` and then free `Node` memory /// while it was still being accessed by the `refresh` function. update_mutex: std.Thread.Mutex = .{}, -/// Keeps track of how many columns in the terminal have been output, so that +/// Keeps track of how many rows in the terminal have been output, so that /// we can move the cursor back later. -columns_written: usize = undefined, +rows_written: usize = 0, + +/// Keeps track of how many cols in the terminal should be output for each row +columns_written: [output_buffer_rows]usize = [_]usize{0} ** output_buffer_rows, + +/// Stores the current max width of the terminal. +/// If not available then 0. +max_columns: usize = 0, + +/// Disable or enable truncating the buffer to the terminal width +respect_terminal_width: bool = true, + +/// Replicate the old one-line style progress bar +emulate_one_line_bar: bool = false, /// Represents one unit of progress. Each node can have children nodes, or /// one can use integers with `update`. @@ -69,17 +88,49 @@ pub const Node = struct { parent: ?*Node, name: []const u8, unit: []const u8 = "", - /// Must be handled atomically to be thread-safe. - recently_updated_child: ?*Node = null, + /// Depth of the Node within the tree + node_tree_depth: usize = undefined, + /// Must be handled using `update_mutex.lock` to be thread-safe. + children: [presentable_children]?*Node = [1]?*Node{null} ** presentable_children, /// Must be handled atomically to be thread-safe. 0 means null. unprotected_estimated_total_items: usize, /// Must be handled atomically to be thread-safe. unprotected_completed_items: usize, + const presentable_children = 10; + + /// Push this `Node` to the `parent.children` stack of the provided `Node` (insert at first index). Thread-safe + fn tryPushToParentStack(self: *Node, target_node: *Node) void { + const parent = target_node.parent orelse return; + if (self.context.emulate_one_line_bar) { + if (parent.children[0] == self) return; + } else { + inline for (parent.children) |child| if (child == self) return; + } + self.context.update_mutex.lock(); // lock below existence check for slight performance reasons + defer self.context.update_mutex.unlock(); // (downside: less precision, but not noticeable) + if (!self.context.emulate_one_line_bar) std.mem.copyBackwards(?*Node, parent.children[1..], parent.children[0 .. parent.children.len - 1]); + parent.children[0] = self; + } + + /// Remove this `Node` from the `parent.children` stack of the provided `Node`. Thread-safe + fn tryRemoveFromParentStack(self: *Node, target_node: *Node) void { + const parent = target_node.parent orelse return; + self.context.update_mutex.lock(); + defer self.context.update_mutex.unlock(); + if (self.context.emulate_one_line_bar) { + parent.children[0] = null; + } else { + const index = std.mem.indexOfScalar(?*Node, parent.children[0..], self) orelse return; + std.mem.copyBackwards(?*Node, parent.children[index..], parent.children[index + 1 ..]); + parent.children[parent.children.len - 1] = null; + } + } + /// Create a new child progress node. Thread-safe. /// Call `Node.end` when done. /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to set `self.parent.recently_updated_child` with the return value. + /// API to set `self.children` with the return value. /// Until that is fixed you probably want to call `activate` on the return value. /// Passing 0 for `estimated_total_items` means unknown. pub fn start(self: *Node, name: []const u8, estimated_total_items: usize) Node { @@ -87,6 +138,7 @@ pub const Node = struct { .context = self.context, .parent = self, .name = name, + .node_tree_depth = self.node_tree_depth + 1, .unprotected_estimated_total_items = estimated_total_items, .unprotected_completed_items = 0, }; @@ -94,9 +146,6 @@ pub const Node = struct { /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. pub fn completeOne(self: *Node) void { - if (self.parent) |parent| { - @atomicStore(?*Node, &parent.recently_updated_child, self, .release); - } _ = @atomicRmw(usize, &self.unprotected_completed_items, .Add, 1, .monotonic); self.context.maybeRefresh(); } @@ -105,11 +154,7 @@ pub const Node = struct { pub fn end(self: *Node) void { self.context.maybeRefresh(); if (self.parent) |parent| { - { - self.context.update_mutex.lock(); - defer self.context.update_mutex.unlock(); - _ = @cmpxchgStrong(?*Node, &parent.recently_updated_child, self, null, .monotonic, .monotonic); - } + self.tryRemoveFromParentStack(self); parent.completeOne(); } else { self.context.update_mutex.lock(); @@ -121,40 +166,34 @@ pub const Node = struct { /// Tell the parent node that this node is actively being worked on. Thread-safe. pub fn activate(self: *Node) void { - if (self.parent) |parent| { - @atomicStore(?*Node, &parent.recently_updated_child, self, .release); - self.context.maybeRefresh(); - } + self.tryPushToParentStack(self); + self.context.maybeRefresh(); } - /// Thread-safe. + /// Tell the parent node that this node is not being worked on anymore. Thread-safe. + pub fn deactivate(self: *Node) void { + self.tryRemoveFromParentStack(self); + self.context.maybeRefresh(); + } + + /// Will also tell the parent node that this node is actively being worked on. Thread-safe. pub fn setName(self: *Node, name: []const u8) void { + self.tryPushToParentStack(self); const progress = self.context; progress.update_mutex.lock(); defer progress.update_mutex.unlock(); self.name = name; - if (self.parent) |parent| { - @atomicStore(?*Node, &parent.recently_updated_child, self, .release); - if (parent.parent) |grand_parent| { - @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .release); - } - if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer); - } + if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer); } - /// Thread-safe. + /// Will also tell the parent node that this node is actively being worked on. Thread-safe. pub fn setUnit(self: *Node, unit: []const u8) void { + self.tryPushToParentStack(self); const progress = self.context; progress.update_mutex.lock(); defer progress.update_mutex.unlock(); self.unit = unit; - if (self.parent) |parent| { - @atomicStore(?*Node, &parent.recently_updated_child, self, .release); - if (parent.parent) |grand_parent| { - @atomicStore(?*Node, &grand_parent.recently_updated_child, parent, .release); - } - if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer); - } + if (progress.timer) |*timer| progress.maybeRefreshWithHeldLock(timer); } /// Thread-safe. 0 means unknown. @@ -190,11 +229,11 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N .context = self, .parent = null, .name = name, + .node_tree_depth = 0, .unprotected_estimated_total_items = estimated_total_items, .unprotected_completed_items = 0, }; - self.columns_written = 0; - self.prev_refresh_timestamp = 0; + if (self.respect_terminal_width) self.max_columns = determineTerminalWidth(self) orelse 0; self.timer = std.time.Timer.start() catch null; self.done = false; return &self.root; @@ -203,6 +242,7 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N /// Updates the terminal if enough time has passed since last update. Thread-safe. pub fn maybeRefresh(self: *Progress) void { if (self.timer) |*timer| { + if (self.respect_terminal_width) self.max_columns = determineTerminalWidth(self) orelse 0; if (!self.update_mutex.tryLock()) return; defer self.update_mutex.unlock(); maybeRefreshWithHeldLock(self, timer); @@ -227,151 +267,216 @@ pub fn refresh(self: *Progress) void { return self.refreshWithHeldLock(); } -fn clearWithHeldLock(p: *Progress, end_ptr: *usize) void { +/// Determine the terminal window width in columns +fn determineTerminalWidth(self: *Progress) ?usize { + if (self.terminal == null) return null; + switch (builtin.os.tag) { + .linux => { + var window_size: std.os.linux.winsize = undefined; + const exit_code = std.os.linux.ioctl(self.terminal.?.handle, std.os.linux.T.IOCGWINSZ, @intFromPtr(&window_size)); + if (exit_code < 0) return null; + return @intCast(window_size.ws_col); + }, + .macos => { + var window_size: std.c.winsize = undefined; + const exit_code = std.c.ioctl(self.terminal.?.handle, std.c.T.IOCGWINSZ, @intFromPtr(&window_size)); + if (exit_code < 0) return null; + return @intCast(window_size.ws_col); + }, + .windows => { + if (!self.is_windows_terminal) return null; + var screen_buffer_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + const exit_code = windows.kernel32.GetConsoleScreenBufferInfo(self.terminal.?.handle, &screen_buffer_info); + if (exit_code != windows.TRUE) return null; + return @intCast(screen_buffer_info.dwSize.X - 1); + }, + else => return null, + } + return null; +} + +/// Clear previously written data and empty the `output_buffer` +fn clearWithHeldLock(p: *Progress) void { const file = p.terminal orelse return; - var end = end_ptr.*; - if (p.columns_written > 0) { - // restore the cursor position by moving the cursor - // `columns_written` cells to the left, then clear the rest of the - // line - if (p.supports_ansi_escape_codes) { - end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[{d}D", .{p.columns_written}) catch unreachable).len; - end += (std.fmt.bufPrint(p.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; - } else if (builtin.os.tag == .windows) winapi: { - std.debug.assert(p.is_windows_terminal); - - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { - // stop trying to write to this file - p.terminal = null; - break :winapi; - } - var cursor_pos = windows.COORD{ - .X = info.dwCursorPosition.X - @as(windows.SHORT, @intCast(p.columns_written)), - .Y = info.dwCursorPosition.Y, - }; + // restore the cursor position by moving the cursor + // `rows_written` cells up, beginning of line, + // then clear to the end of the screen + if (p.supports_ansi_escape_codes) { + var buffer: [20]u8 = undefined; + var end: usize = 0; - if (cursor_pos.X < 0) - cursor_pos.X = 0; + if (p.rows_written == 0) { + end += (std.fmt.bufPrint(buffer[end..], "\x1b[0G", .{}) catch unreachable).len; // beginning of line + end += (std.fmt.bufPrint(buffer[end..], "\x1b[0K", .{}) catch unreachable).len; // clear till end of line + } else { + end += (std.fmt.bufPrint(buffer[end..], "\x1b[{d}F", .{p.rows_written}) catch unreachable).len; // move up and to start + end += (std.fmt.bufPrint(buffer[end..], "\x1b[0J", .{}) catch unreachable).len; // clear till end of screen + } - const fill_chars = @as(windows.DWORD, @intCast(info.dwSize.X - cursor_pos.X)); + _ = file.write(buffer[0..end]) catch { + // stop trying to write to this file + p.terminal = null; + }; + } else if (builtin.os.tag == .windows and p.is_windows_terminal) winapi: { + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { + // stop trying to write to this file + p.terminal = null; + break :winapi; + } + const cursor_pos = windows.COORD{ + .X = 0, + .Y = info.dwCursorPosition.Y - @as(windows.SHORT, @intCast(p.rows_written)), + }; - var written: windows.DWORD = undefined; - if (windows.kernel32.FillConsoleOutputAttribute( - file.handle, - info.wAttributes, - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) { - // stop trying to write to this file - p.terminal = null; - break :winapi; - } - if (windows.kernel32.FillConsoleOutputCharacterW( - file.handle, - ' ', - fill_chars, - cursor_pos, - &written, - ) != windows.TRUE) { - // stop trying to write to this file - p.terminal = null; - break :winapi; - } - if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) { - // stop trying to write to this file - p.terminal = null; - break :winapi; - } - } else { - // we are in a "dumb" terminal like in acme or writing to a file - p.output_buffer[end] = '\n'; - end += 1; + const fill_chars = @as(windows.DWORD, @intCast(info.dwSize.Y - cursor_pos.Y)); + + var written: windows.DWORD = undefined; + if (windows.kernel32.FillConsoleOutputAttribute( + file.handle, + info.wAttributes, + fill_chars, + cursor_pos, + &written, + ) != windows.TRUE) { + // stop trying to write to this file + p.terminal = null; + break :winapi; + } + if (windows.kernel32.FillConsoleOutputCharacterW( + file.handle, + ' ', + fill_chars, + cursor_pos, + &written, + ) != windows.TRUE) { + // stop trying to write to this file + p.terminal = null; + break :winapi; } + if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE) { + // stop trying to write to this file + p.terminal = null; + break :winapi; + } + } else { + // we are in a "dumb" terminal like in acme or writing to a file + _ = file.write("\n") catch { + p.terminal = null; + }; + } + p.rows_written = 0; + p.columns_written[0] = 0; +} - p.columns_written = 0; +/// Write the `output_buffer` to the terminal +/// Together with `clearWithHeldLock` this method flushes the buffer +fn writeOutputBufferToFile(p: *Progress) void { + const file = p.terminal orelse return; + + for (p.output_buffer[0 .. p.rows_written + 1], 0..) |output_row, row_index| { + // Join the rows with LFs without requiring a large buffer + if (row_index != 0) { + _ = file.write("\n") catch { + p.terminal = null; + break; + }; + } + _ = file.write(output_row[0..p.columns_written[row_index]]) catch { + // stop trying to write to this file + p.terminal = null; + break; + }; } - end_ptr.* = end; } fn refreshWithHeldLock(self: *Progress) void { const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal; if (is_dumb and self.dont_print_on_dumb) return; - const file = self.terminal orelse return; - - var end: usize = 0; - clearWithHeldLock(self, &end); + clearWithHeldLock(self); if (!self.done) { - var need_ellipse = false; - var maybe_node: ?*Node = &self.root; - while (maybe_node) |node| { - if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); + var need_newline = false; + refreshOutputBufWithHeldLock(self, &self.root, &need_newline); + } + + writeOutputBufferToFile(self); + if (self.timer) |*timer| { + self.prev_refresh_timestamp = timer.read(); + } +} + +fn refreshOutputBufWithHeldLock(self: *Progress, node: *Node, need_newline: *bool) void { + var need_ellipse = false; + + const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); + const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .monotonic); + const current_item = completed_items + 1; + + if (node.name.len != 0 or eti > 0) { + if (need_newline.*) { + if (self.emulate_one_line_bar) { + self.bufWrite(" ", .{}); + } else { + self.bufWriteLineFeed(); } + need_newline.* = false; + } + if (node.node_tree_depth > 0 and !self.emulate_one_line_bar) { + const depth: usize = @min(10, node.node_tree_depth); + const whitespace_length: usize = if (depth > 1) (depth - 1) * 2 else 0; + self.bufWrite("{s: <[3]}{s}{c} ", .{ + "", + if (node.node_tree_depth > 10) "_ " else "", + self.bullet, + whitespace_length, + }); + } + if (node.name.len != 0) { + self.bufWrite("{s} ", .{node.name}); + need_ellipse = true; + } + if (eti > 0) { + self.bufWrite("[{d}/{d}{s}]", .{ current_item, eti, node.unit }); + need_ellipse = false; + } else if (completed_items != 0) { + self.bufWrite("[{d}{s}]", .{ current_item, node.unit }); need_ellipse = false; - const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); - const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .monotonic); - const current_item = completed_items + 1; - if (node.name.len != 0 or eti > 0) { - if (node.name.len != 0) { - self.bufWrite(&end, "{s}", .{node.name}); - need_ellipse = true; - } - if (eti > 0) { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{d}/{d}{s}] ", .{ current_item, eti, node.unit }); - need_ellipse = false; - } else if (completed_items != 0) { - if (need_ellipse) self.bufWrite(&end, " ", .{}); - self.bufWrite(&end, "[{d}{s}] ", .{ current_item, node.unit }); - need_ellipse = false; - } - } - maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .acquire); } if (need_ellipse) { - self.bufWrite(&end, "... ", .{}); + self.bufWrite("...", .{}); } + + need_newline.* = true; } - _ = file.write(self.output_buffer[0..end]) catch { - // stop trying to write to this file - self.terminal = null; - }; - if (self.timer) |*timer| { - self.prev_refresh_timestamp = timer.read(); + for (node.children[0..if (self.emulate_one_line_bar) 1 else node.children.len]) |maybe_child| { + if (maybe_child) |child| refreshOutputBufWithHeldLock(self, child, need_newline) else break; } } +/// Print to the terminal, temporarily stopping the progress bar from flushing the buffer pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { + self.lock_stderr(); + defer self.unlock_stderr(); const file = self.terminal orelse { std.debug.print(format, args); return; }; - self.refresh(); file.writer().print(format, args) catch { self.terminal = null; return; }; - self.columns_written = 0; } /// Allows the caller to freely write to stderr until unlock_stderr() is called. /// During the lock, the progress information is cleared from the terminal. pub fn lock_stderr(p: *Progress) void { p.update_mutex.lock(); - if (p.terminal) |file| { - var end: usize = 0; - clearWithHeldLock(p, &end); - _ = file.write(p.output_buffer[0..end]) catch { - // stop trying to write to this file - p.terminal = null; - }; - } + clearWithHeldLock(p); + writeOutputBufferToFile(p); std.debug.getStderrMutex().lock(); } @@ -380,19 +485,35 @@ pub fn unlock_stderr(p: *Progress) void { p.update_mutex.unlock(); } -fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { - if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { - const amt = written.len; - end.* += amt; - self.columns_written += amt; +/// Move to the next row in the buffer and reset the cursor position to 0 +/// Ignores request if buffer is full +fn bufWriteLineFeed(self: *Progress) void { + if (self.rows_written + 1 >= self.columns_written.len) return; + self.rows_written += 1; + self.columns_written[self.rows_written] = 0; +} + +/// Append to the current row stored in the buffer +fn bufWrite(self: *Progress, comptime format: []const u8, args: anytype) void { + comptime std.debug.assert(std.mem.count(u8, format, "\n") == 0); + const output_row = &self.output_buffer[self.rows_written]; + const columns_written = &self.columns_written[self.rows_written]; + + if (std.fmt.bufPrint(output_row[columns_written.*..], format, args)) |written| { + columns_written.* += written.len; } else |err| switch (err) { error.NoSpaceLeft => { - self.columns_written += self.output_buffer.len - end.*; - end.* = self.output_buffer.len; - const suffix = "... "; - @memcpy(self.output_buffer[self.output_buffer.len - suffix.len ..], suffix); + columns_written.* = output_row.len; + const suffix = "..."; + @memcpy(output_row.*[output_row.*.len - suffix.len ..], suffix); }, } + + if (self.max_columns != 0 and columns_written.* > self.max_columns) { + const ellipse = "..."; + columns_written.* = self.max_columns; + if (columns_written.* >= ellipse.len) @memcpy(output_row.*[columns_written.* - ellipse.len .. columns_written.*], ellipse); + } } test "basic functionality" { diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 10e14a55fc68..8f93d9b8cafa 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -134,7 +134,7 @@ pub fn receiveBody_u32(s: *Server) !u32 { return bswap(result); } -pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { +pub fn serveStringMessage(s: *const Server, tag: OutMessage.Tag, msg: []const u8) !void { return s.serveMessage(.{ .tag = tag, .bytes_len = @as(u32, @intCast(msg.len)), diff --git a/src/main.zig b/src/main.zig index 5b0e211647cf..bf47565f2ee8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4031,10 +4031,9 @@ fn serve( .unprotected_estimated_total_items = 0, .unprotected_completed_items = 0, }, - .columns_written = 0, - .prev_refresh_timestamp = 0, .timer = null, .done = false, + .emulate_one_line_bar = true, }; const main_progress_node = &progress.root; main_progress_node.context = &progress; @@ -4045,7 +4044,7 @@ fn serve( switch (hdr.tag) { .exit => return cleanExit(), .update => { - assert(main_progress_node.recently_updated_child == null); + for (main_progress_node.children) |child| assert(child == null); tracy.frameMark(); if (arg_mode == .translate_c) { @@ -4110,7 +4109,7 @@ fn serve( }, .hot_update => { tracy.frameMark(); - assert(main_progress_node.recently_updated_child == null); + for (main_progress_node.children) |child| assert(child == null); if (child_pid) |pid| { try comp.hotCodeSwap(main_progress_node, pid); try serveUpdateResults(&server, comp); @@ -4149,51 +4148,19 @@ fn progressThread(progress: *std.Progress, server: *const Server, reset: *std.Th error.Timeout => {}, } - var buf: std.BoundedArray(u8, 160) = .{}; + progress.refresh(); - { - progress.update_mutex.lock(); - defer progress.update_mutex.unlock(); - - var need_ellipse = false; - var maybe_node: ?*std.Progress.Node = &progress.root; - while (maybe_node) |node| { - if (need_ellipse) { - buf.appendSlice("... ") catch {}; - } - need_ellipse = false; - const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .monotonic); - const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .monotonic); - const current_item = completed_items + 1; - if (node.name.len != 0 or eti > 0) { - if (node.name.len != 0) { - buf.appendSlice(node.name) catch {}; - need_ellipse = true; - } - if (eti > 0) { - if (need_ellipse) buf.appendSlice(" ") catch {}; - buf.writer().print("[{d}/{d}] ", .{ current_item, eti }) catch {}; - need_ellipse = false; - } else if (completed_items != 0) { - if (need_ellipse) buf.appendSlice(" ") catch {}; - buf.writer().print("[{d}] ", .{current_item}) catch {}; - need_ellipse = false; - } - } - maybe_node = @atomicLoad(?*std.Progress.Node, &node.recently_updated_child, .acquire); + // TODO: Provisional. This should be better integrated. + for (progress.output_buffer[0 .. progress.rows_written + 1], 0..) |output_row, row_index| { + if (row_index != 0) { + server.serveStringMessage(.progress, "\n") catch |err| { + fatal("unable to write to client: {s}", .{@errorName(err)}); + }; } + server.serveStringMessage(.progress, output_row[0..progress.columns_written[row_index]]) catch |err| { + fatal("unable to write to client: {s}", .{@errorName(err)}); + }; } - - const progress_string = buf.slice(); - - server.serveMessage(.{ - .tag = .progress, - .bytes_len = @as(u32, @intCast(progress_string.len)), - }, &.{ - progress_string, - }) catch |err| { - fatal("unable to write to client: {s}", .{@errorName(err)}); - }; } } @@ -4466,7 +4433,7 @@ fn runOrTestHotSwap( fn updateModule(comp: *Compilation, color: Color) !void { { // If the terminal is dumb, we dont want to show the user all the output. - var progress: std.Progress = .{ .dont_print_on_dumb = true }; + var progress: std.Progress = .{ .dont_print_on_dumb = true, .emulate_one_line_bar = true }; const main_progress_node = progress.start("", 0); defer main_progress_node.end(); switch (color) { @@ -4730,7 +4697,7 @@ const usage_build = ; fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { - var progress: std.Progress = .{ .dont_print_on_dumb = true }; + var progress: std.Progress = .{ .dont_print_on_dumb = true, .emulate_one_line_bar = true }; var build_file: ?[]const u8 = null; var override_lib_dir: ?[]const u8 = try EnvVar.ZIG_LIB_DIR.get(arena); @@ -5475,10 +5442,10 @@ fn jitCmd( .unprotected_estimated_total_items = 0, .unprotected_completed_items = 0, }, - .columns_written = 0, .prev_refresh_timestamp = 0, .timer = null, .done = false, + .emulate_one_line_bar = true, }; const main_progress_node = &progress.root; main_progress_node.context = &progress; @@ -6812,7 +6779,7 @@ fn cmdFetch( try http_client.initDefaultProxies(arena); - var progress: std.Progress = .{ .dont_print_on_dumb = true }; + var progress: std.Progress = .{ .dont_print_on_dumb = true, .emulate_one_line_bar = true }; const root_prog_node = progress.start("Fetch", 0); defer root_prog_node.end(); diff --git a/test/src/Cases.zig b/test/src/Cases.zig index f84d0d61dbf5..3fae06e0432b 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -1438,7 +1438,7 @@ fn resolveTargetQuery(query: std.Target.Query) std.Build.ResolvedTarget { fn runCases(self: *Cases, zig_exe_path: []const u8) !void { const host = try std.zig.system.resolveTargetQuery(.{}); - var progress = std.Progress{}; + var progress = std.Progress{ .emulate_one_line_bar = true }; const root_node = progress.start("compiler", self.cases.items.len); progress.terminal = null; defer root_node.end(); diff --git a/tools/doctest.zig b/tools/doctest.zig index 6be9d59ffbd0..b63796e1620f 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -1011,7 +1011,11 @@ fn termColor(allocator: Allocator, input: []const u8) ![]u8 { }, .after_number => switch (c) { ';' => state = .arg, - 'D' => state = .start, + 'A'...'G' => state = .start, + 'J' => { + buf.items.len = last_new_line; + state = .start; + }, 'K' => { buf.items.len = last_new_line; state = .start; diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 662f28c6f75d..fe6efdebfe34 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1022,7 +1022,7 @@ pub fn main() anyerror!void { var zig_src_dir = try fs.cwd().openDir(zig_src_root, .{}); defer zig_src_dir.close(); - var progress = std.Progress{}; + var progress = std.Progress{ .emulate_one_line_bar = true }; const root_progress = progress.start("", llvm_targets.len); defer root_progress.end();