| ... | ... | @@ -23,17 +23,13 @@ redraw_event: std.Thread.ResetEvent, |
| 23 | 23 | /// Indicates a request to shut down and reset global state. |
| 24 | 24 | /// Accessed atomically. |
| 25 | 25 | done: bool, |
| 26 | need_clear: bool, |
| 26 | 27 | |
| 27 | 28 | refresh_rate_ns: u64, |
| 28 | 29 | initial_delay_ns: u64, |
| 29 | 30 | |
| 30 | 31 | rows: u16, |
| 31 | 32 | cols: u16, |
| 32 | | /// Tracks the number of newlines that have been actually written to the terminal. |
| 33 | | written_newline_count: u16, |
| 34 | | /// Tracks the number of newlines that will be written to the terminal if the |
| 35 | | /// draw buffer is sent. |
| 36 | | accumulated_newline_count: u16, |
| 37 | 33 | |
| 38 | 34 | /// Accessed only by the update thread. |
| 39 | 35 | draw_buffer: []u8, |
| ... | ... | @@ -312,10 +308,9 @@ var global_progress: Progress = .{ |
| 312 | 308 | .initial_delay_ns = undefined, |
| 313 | 309 | .rows = 0, |
| 314 | 310 | .cols = 0, |
| 315 | | .written_newline_count = 0, |
| 316 | | .accumulated_newline_count = 0, |
| 317 | 311 | .draw_buffer = undefined, |
| 318 | 312 | .done = false, |
| 313 | .need_clear = false, |
| 319 | 314 | |
| 320 | 315 | .node_parents = &node_parents_buffer, |
| 321 | 316 | .node_storage = &node_storage_buffer, |
| ... | ... | @@ -446,10 +441,11 @@ fn updateThreadRun() void { |
| 446 | 441 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; |
| 447 | 442 | maybeUpdateSize(resize_flag); |
| 448 | 443 | |
| 449 | | const buffer = computeRedraw(&serialized_buffer); |
| 444 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 450 | 445 | if (stderr_mutex.tryLock()) { |
| 451 | 446 | defer stderr_mutex.unlock(); |
| 452 | 447 | write(buffer) catch return; |
| 448 | global_progress.need_clear = true; |
| 453 | 449 | } |
| 454 | 450 | } |
| 455 | 451 | |
| ... | ... | @@ -464,10 +460,11 @@ fn updateThreadRun() void { |
| 464 | 460 | |
| 465 | 461 | maybeUpdateSize(resize_flag); |
| 466 | 462 | |
| 467 | | const buffer = computeRedraw(&serialized_buffer); |
| 463 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 468 | 464 | if (stderr_mutex.tryLock()) { |
| 469 | 465 | defer stderr_mutex.unlock(); |
| 470 | 466 | write(buffer) catch return; |
| 467 | global_progress.need_clear = true; |
| 471 | 468 | } |
| 472 | 469 | } |
| 473 | 470 | } |
| ... | ... | @@ -488,11 +485,13 @@ fn windowsApiUpdateThreadRun() void { |
| 488 | 485 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; |
| 489 | 486 | maybeUpdateSize(resize_flag); |
| 490 | 487 | |
| 491 | | const buffer = computeRedraw(&serialized_buffer); |
| 488 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 492 | 489 | if (stderr_mutex.tryLock()) { |
| 493 | 490 | defer stderr_mutex.unlock(); |
| 494 | 491 | windowsApiWriteMarker(); |
| 495 | 492 | write(buffer) catch return; |
| 493 | global_progress.need_clear = true; |
| 494 | windowsApiMoveToMarker(nl_n) catch return; |
| 496 | 495 | } |
| 497 | 496 | } |
| 498 | 497 | |
| ... | ... | @@ -507,12 +506,14 @@ fn windowsApiUpdateThreadRun() void { |
| 507 | 506 | |
| 508 | 507 | maybeUpdateSize(resize_flag); |
| 509 | 508 | |
| 510 | | const buffer = computeRedraw(&serialized_buffer); |
| 509 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 511 | 510 | if (stderr_mutex.tryLock()) { |
| 512 | 511 | defer stderr_mutex.unlock(); |
| 513 | 512 | clearWrittenWindowsApi() catch return; |
| 514 | 513 | windowsApiWriteMarker(); |
| 515 | 514 | write(buffer) catch return; |
| 515 | global_progress.need_clear = true; |
| 516 | windowsApiMoveToMarker(nl_n) catch return; |
| 516 | 517 | } |
| 517 | 518 | } |
| 518 | 519 | } |
| ... | ... | @@ -645,40 +646,16 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { |
| 645 | 646 | } |
| 646 | 647 | |
| 647 | 648 | fn clearWrittenWithEscapeCodes() anyerror!void { |
| 648 | | if (global_progress.written_newline_count == 0) return; |
| 649 | if (!global_progress.need_clear) return; |
| 649 | 650 | |
| 650 | 651 | var i: usize = 0; |
| 651 | 652 | const buf = global_progress.draw_buffer; |
| 652 | 653 | |
| 653 | | buf[i..][0..start_sync.len].* = start_sync.*; |
| 654 | | i += start_sync.len; |
| 655 | | |
| 656 | | i = computeClear(buf, i); |
| 657 | | |
| 658 | | buf[i..][0..finish_sync.len].* = finish_sync.*; |
| 659 | | i += finish_sync.len; |
| 660 | | |
| 661 | | global_progress.accumulated_newline_count = 0; |
| 662 | | try write(buf[0..i]); |
| 663 | | } |
| 664 | | |
| 665 | | fn computeClear(buf: []u8, start_i: usize) usize { |
| 666 | | var i = start_i; |
| 667 | | |
| 668 | | const prev_nl_n = global_progress.written_newline_count; |
| 669 | | if (prev_nl_n > 0) { |
| 670 | | buf[i] = '\r'; |
| 671 | | i += 1; |
| 672 | | for (0..prev_nl_n) |_| { |
| 673 | | buf[i..][0..up_one_line.len].* = up_one_line.*; |
| 674 | | i += up_one_line.len; |
| 675 | | } |
| 676 | | } |
| 677 | | |
| 678 | 654 | buf[i..][0..clear.len].* = clear.*; |
| 679 | 655 | i += clear.len; |
| 680 | 656 | |
| 681 | | return i; |
| 657 | global_progress.need_clear = false; |
| 658 | try write(buf[0..i]); |
| 682 | 659 | } |
| 683 | 660 | |
| 684 | 661 | /// U+25BA or ► |
| ... | ... | @@ -704,38 +681,44 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { |
| 704 | 681 | // but it must be a valid attribute and it actually needs to apply to the first |
| 705 | 682 | // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem |
| 706 | 683 | // like any of the available attributes are invisible/benign. |
| 707 | | const prev_nl_n = global_progress.written_newline_count; |
| 708 | | if (prev_nl_n > 0) { |
| 709 | | const handle = global_progress.terminal.handle; |
| 710 | | const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; |
| 684 | if (!global_progress.need_clear) return; |
| 685 | const handle = global_progress.terminal.handle; |
| 686 | const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; |
| 711 | 687 | |
| 712 | | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; |
| 713 | | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { |
| 714 | | return error.Unexpected; |
| 715 | | } |
| 716 | | const cursor_pos = console_info.dwCursorPosition; |
| 717 | | const expected_y = cursor_pos.Y - @as(i16, @intCast(prev_nl_n)); |
| 718 | | var start_pos = windows.COORD{ .X = 0, .Y = expected_y }; |
| 719 | | while (start_pos.Y >= 0) { |
| 720 | | var wchar: [1]u16 = undefined; |
| 721 | | var num_console_chars_read: windows.DWORD = undefined; |
| 722 | | if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) { |
| 723 | | return error.Unexpected; |
| 724 | | } |
| 688 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; |
| 689 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { |
| 690 | return error.Unexpected; |
| 691 | } |
| 692 | var num_chars_written: windows.DWORD = undefined; |
| 693 | if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, console_info.dwCursorPosition, &num_chars_written) == 0) { |
| 694 | return error.Unexpected; |
| 695 | } |
| 696 | } |
| 725 | 697 | |
| 726 | | if (wchar[0] == windows_api_start_marker) break; |
| 727 | | start_pos.Y -= 1; |
| 728 | | } else { |
| 729 | | // If we couldn't find the marker, then just assume that no lines wrapped |
| 730 | | start_pos = .{ .X = 0, .Y = expected_y }; |
| 731 | | } |
| 732 | | var num_chars_written: windows.DWORD = undefined; |
| 733 | | if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, start_pos, &num_chars_written) == 0) { |
| 734 | | return error.Unexpected; |
| 735 | | } |
| 736 | | if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) { |
| 698 | fn windowsApiMoveToMarker(nl_n: usize) error{Unexpected}!void { |
| 699 | const handle = global_progress.terminal.handle; |
| 700 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; |
| 701 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { |
| 702 | return error.Unexpected; |
| 703 | } |
| 704 | const cursor_pos = console_info.dwCursorPosition; |
| 705 | const expected_y = cursor_pos.Y - @as(i16, @intCast(nl_n)); |
| 706 | var start_pos: windows.COORD = .{ .X = 0, .Y = expected_y }; |
| 707 | while (start_pos.Y >= 0) { |
| 708 | var wchar: [1]u16 = undefined; |
| 709 | var num_console_chars_read: windows.DWORD = undefined; |
| 710 | if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) { |
| 737 | 711 | return error.Unexpected; |
| 738 | 712 | } |
| 713 | |
| 714 | if (wchar[0] == windows_api_start_marker) break; |
| 715 | start_pos.Y -= 1; |
| 716 | } else { |
| 717 | // If we couldn't find the marker, then just assume that no lines wrapped |
| 718 | start_pos = .{ .X = 0, .Y = expected_y }; |
| 719 | } |
| 720 | if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) { |
| 721 | return error.Unexpected; |
| 739 | 722 | } |
| 740 | 723 | } |
| 741 | 724 | |
| ... | ... | @@ -1052,7 +1035,7 @@ fn useSavedIpcData( |
| 1052 | 1035 | return start_serialized_len + storage.len; |
| 1053 | 1036 | } |
| 1054 | 1037 | |
| 1055 | | fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 { |
| 1038 | fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { |
| 1056 | 1039 | const serialized = serialize(serialized_buffer); |
| 1057 | 1040 | |
| 1058 | 1041 | // Now we can analyze our copy of the graph without atomics, reconstructing |
| ... | ... | @@ -1078,8 +1061,10 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 { |
| 1078 | 1061 | } |
| 1079 | 1062 | } |
| 1080 | 1063 | |
| 1081 | | // The strategy is: keep the cursor at the end, and then with every redraw: |
| 1082 | | // move cursor to beginning of line, move cursor up N lines, erase to end of screen, write |
| 1064 | // The strategy is, with every redraw: |
| 1065 | // erase to end of screen, write, move cursor to beginning of line, move cursor up N lines |
| 1066 | // This keeps the cursor at the beginning so that unlocked stderr writes |
| 1067 | // don't get eaten by the clear. |
| 1083 | 1068 | |
| 1084 | 1069 | var i: usize = 0; |
| 1085 | 1070 | const buf = global_progress.draw_buffer; |
| ... | ... | @@ -1091,20 +1076,31 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 { |
| 1091 | 1076 | |
| 1092 | 1077 | switch (global_progress.terminal_mode) { |
| 1093 | 1078 | .off => unreachable, |
| 1094 | | .ansi_escape_codes => i = computeClear(buf, i), |
| 1079 | .ansi_escape_codes => { |
| 1080 | buf[i..][0..clear.len].* = clear.*; |
| 1081 | i += clear.len; |
| 1082 | }, |
| 1095 | 1083 | .windows_api => if (!is_windows) unreachable, |
| 1096 | 1084 | } |
| 1097 | 1085 | |
| 1098 | | global_progress.accumulated_newline_count = 0; |
| 1099 | 1086 | const root_node_index: Node.Index = @enumFromInt(0); |
| 1100 | | i = computeNode(buf, i, serialized, children, root_node_index); |
| 1087 | i, const nl_n = computeNode(buf, i, 0, serialized, children, root_node_index); |
| 1101 | 1088 | |
| 1102 | 1089 | if (global_progress.terminal_mode == .ansi_escape_codes) { |
| 1090 | if (nl_n > 0) { |
| 1091 | buf[i] = '\r'; |
| 1092 | i += 1; |
| 1093 | for (0..nl_n) |_| { |
| 1094 | buf[i..][0..up_one_line.len].* = up_one_line.*; |
| 1095 | i += up_one_line.len; |
| 1096 | } |
| 1097 | } |
| 1098 | |
| 1103 | 1099 | buf[i..][0..finish_sync.len].* = finish_sync.*; |
| 1104 | 1100 | i += finish_sync.len; |
| 1105 | 1101 | } |
| 1106 | 1102 | |
| 1107 | | return buf[0..i]; |
| 1103 | return .{ buf[0..i], nl_n }; |
| 1108 | 1104 | } |
| 1109 | 1105 | |
| 1110 | 1106 | fn computePrefix( |
| ... | ... | @@ -1138,20 +1134,23 @@ fn computePrefix( |
| 1138 | 1134 | } |
| 1139 | 1135 | |
| 1140 | 1136 | const line_upper_bound_len = @max(TreeSymbol.tee.maxByteLen(), TreeSymbol.langle.maxByteLen()) + |
| 1141 | | "[4294967296/4294967296] ".len + Node.max_name_len + finish_sync.len; |
| 1137 | "[4294967296/4294967296] ".len + Node.max_name_len + (1 + up_one_line.len) + finish_sync.len; |
| 1142 | 1138 | |
| 1143 | 1139 | fn computeNode( |
| 1144 | 1140 | buf: []u8, |
| 1145 | 1141 | start_i: usize, |
| 1142 | start_nl_n: usize, |
| 1146 | 1143 | serialized: Serialized, |
| 1147 | 1144 | children: []const Children, |
| 1148 | 1145 | node_index: Node.Index, |
| 1149 | | ) usize { |
| 1146 | ) struct { usize, usize } { |
| 1150 | 1147 | var i = start_i; |
| 1148 | var nl_n = start_nl_n; |
| 1149 | |
| 1151 | 1150 | i = computePrefix(buf, i, serialized, children, node_index); |
| 1152 | 1151 | |
| 1153 | 1152 | if (i + line_upper_bound_len > buf.len) |
| 1154 | | return start_i; |
| 1153 | return .{ start_i, start_nl_n }; |
| 1155 | 1154 | |
| 1156 | 1155 | const storage = &serialized.storage[@intFromEnum(node_index)]; |
| 1157 | 1156 | const estimated_total = storage.estimated_total_count; |
| ... | ... | @@ -1186,34 +1185,33 @@ fn computeNode( |
| 1186 | 1185 | i = @min(global_progress.cols + start_i, i); |
| 1187 | 1186 | buf[i] = '\n'; |
| 1188 | 1187 | i += 1; |
| 1189 | | global_progress.accumulated_newline_count += 1; |
| 1188 | nl_n += 1; |
| 1190 | 1189 | } |
| 1191 | 1190 | |
| 1192 | | if (global_progress.withinRowLimit()) { |
| 1191 | if (global_progress.withinRowLimit(nl_n)) { |
| 1193 | 1192 | if (children[@intFromEnum(node_index)].child.unwrap()) |child| { |
| 1194 | | i = computeNode(buf, i, serialized, children, child); |
| 1193 | i, nl_n = computeNode(buf, i, nl_n, serialized, children, child); |
| 1195 | 1194 | } |
| 1196 | 1195 | } |
| 1197 | 1196 | |
| 1198 | | if (global_progress.withinRowLimit()) { |
| 1197 | if (global_progress.withinRowLimit(nl_n)) { |
| 1199 | 1198 | if (children[@intFromEnum(node_index)].sibling.unwrap()) |sibling| { |
| 1200 | | i = computeNode(buf, i, serialized, children, sibling); |
| 1199 | i, nl_n = computeNode(buf, i, nl_n, serialized, children, sibling); |
| 1201 | 1200 | } |
| 1202 | 1201 | } |
| 1203 | 1202 | |
| 1204 | | return i; |
| 1203 | return .{ i, nl_n }; |
| 1205 | 1204 | } |
| 1206 | 1205 | |
| 1207 | | fn withinRowLimit(p: *Progress) bool { |
| 1206 | fn withinRowLimit(p: *Progress, nl_n: usize) bool { |
| 1208 | 1207 | // The +2 here is so that the PS1 is not scrolled off the top of the terminal. |
| 1209 | 1208 | // one because we keep the cursor on the next line |
| 1210 | 1209 | // one more to account for the PS1 |
| 1211 | | return p.accumulated_newline_count + 2 < p.rows; |
| 1210 | return nl_n + 2 < p.rows; |
| 1212 | 1211 | } |
| 1213 | 1212 | |
| 1214 | 1213 | fn write(buf: []const u8) anyerror!void { |
| 1215 | 1214 | try global_progress.terminal.writeAll(buf); |
| 1216 | | global_progress.written_newline_count = global_progress.accumulated_newline_count; |
| 1217 | 1215 | } |
| 1218 | 1216 | |
| 1219 | 1217 | var remaining_write_trash_bytes: usize = 0; |