| ... | @@ -23,17 +23,13 @@ redraw_event: std.Thread.ResetEvent, | ... | @@ -23,17 +23,13 @@ redraw_event: std.Thread.ResetEvent, |
| 23 | /// Indicates a request to shut down and reset global state. | 23 | /// Indicates a request to shut down and reset global state. |
| 24 | /// Accessed atomically. | 24 | /// Accessed atomically. |
| 25 | done: bool, | 25 | done: bool, |
| | 26 | need_clear: bool, |
| 26 | | 27 | |
| 27 | refresh_rate_ns: u64, | 28 | refresh_rate_ns: u64, |
| 28 | initial_delay_ns: u64, | 29 | initial_delay_ns: u64, |
| 29 | | 30 | |
| 30 | rows: u16, | 31 | rows: u16, |
| 31 | cols: u16, | 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 | /// Accessed only by the update thread. | 34 | /// Accessed only by the update thread. |
| 39 | draw_buffer: []u8, | 35 | draw_buffer: []u8, |
| ... | @@ -312,10 +308,9 @@ var global_progress: Progress = .{ | ... | @@ -312,10 +308,9 @@ var global_progress: Progress = .{ |
| 312 | .initial_delay_ns = undefined, | 308 | .initial_delay_ns = undefined, |
| 313 | .rows = 0, | 309 | .rows = 0, |
| 314 | .cols = 0, | 310 | .cols = 0, |
| 315 | .written_newline_count = 0, | | |
| 316 | .accumulated_newline_count = 0, | | |
| 317 | .draw_buffer = undefined, | 311 | .draw_buffer = undefined, |
| 318 | .done = false, | 312 | .done = false, |
| | 313 | .need_clear = false, |
| 319 | | 314 | |
| 320 | .node_parents = &node_parents_buffer, | 315 | .node_parents = &node_parents_buffer, |
| 321 | .node_storage = &node_storage_buffer, | 316 | .node_storage = &node_storage_buffer, |
| ... | @@ -446,10 +441,11 @@ fn updateThreadRun() void { | ... | @@ -446,10 +441,11 @@ fn updateThreadRun() void { |
| 446 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; | 441 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; |
| 447 | maybeUpdateSize(resize_flag); | 442 | maybeUpdateSize(resize_flag); |
| 448 | | 443 | |
| 449 | const buffer = computeRedraw(&serialized_buffer); | 444 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 450 | if (stderr_mutex.tryLock()) { | 445 | if (stderr_mutex.tryLock()) { |
| 451 | defer stderr_mutex.unlock(); | 446 | defer stderr_mutex.unlock(); |
| 452 | write(buffer) catch return; | 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,10 +460,11 @@ fn updateThreadRun() void { |
| 464 | | 460 | |
| 465 | maybeUpdateSize(resize_flag); | 461 | maybeUpdateSize(resize_flag); |
| 466 | | 462 | |
| 467 | const buffer = computeRedraw(&serialized_buffer); | 463 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 468 | if (stderr_mutex.tryLock()) { | 464 | if (stderr_mutex.tryLock()) { |
| 469 | defer stderr_mutex.unlock(); | 465 | defer stderr_mutex.unlock(); |
| 470 | write(buffer) catch return; | 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,11 +485,13 @@ fn windowsApiUpdateThreadRun() void { |
| 488 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; | 485 | if (@atomicLoad(bool, &global_progress.done, .seq_cst)) return; |
| 489 | maybeUpdateSize(resize_flag); | 486 | maybeUpdateSize(resize_flag); |
| 490 | | 487 | |
| 491 | const buffer = computeRedraw(&serialized_buffer); | 488 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 492 | if (stderr_mutex.tryLock()) { | 489 | if (stderr_mutex.tryLock()) { |
| 493 | defer stderr_mutex.unlock(); | 490 | defer stderr_mutex.unlock(); |
| 494 | windowsApiWriteMarker(); | 491 | windowsApiWriteMarker(); |
| 495 | write(buffer) catch return; | 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,12 +506,14 @@ fn windowsApiUpdateThreadRun() void { |
| 507 | | 506 | |
| 508 | maybeUpdateSize(resize_flag); | 507 | maybeUpdateSize(resize_flag); |
| 509 | | 508 | |
| 510 | const buffer = computeRedraw(&serialized_buffer); | 509 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 511 | if (stderr_mutex.tryLock()) { | 510 | if (stderr_mutex.tryLock()) { |
| 512 | defer stderr_mutex.unlock(); | 511 | defer stderr_mutex.unlock(); |
| 513 | clearWrittenWindowsApi() catch return; | 512 | clearWrittenWindowsApi() catch return; |
| 514 | windowsApiWriteMarker(); | 513 | windowsApiWriteMarker(); |
| 515 | write(buffer) catch return; | 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,40 +646,16 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { |
| 645 | } | 646 | } |
| 646 | | 647 | |
| 647 | fn clearWrittenWithEscapeCodes() anyerror!void { | 648 | fn clearWrittenWithEscapeCodes() anyerror!void { |
| 648 | if (global_progress.written_newline_count == 0) return; | 649 | if (!global_progress.need_clear) return; |
| 649 | | 650 | |
| 650 | var i: usize = 0; | 651 | var i: usize = 0; |
| 651 | const buf = global_progress.draw_buffer; | 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 | buf[i..][0..clear.len].* = clear.*; | 654 | buf[i..][0..clear.len].* = clear.*; |
| 679 | i += clear.len; | 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 | /// U+25BA or ► | 661 | /// U+25BA or ► |
| ... | @@ -704,38 +681,44 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { | ... | @@ -704,38 +681,44 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { |
| 704 | // but it must be a valid attribute and it actually needs to apply to the first | 681 | // but it must be a valid attribute and it actually needs to apply to the first |
| 705 | // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem | 682 | // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem |
| 706 | // like any of the available attributes are invisible/benign. | 683 | // like any of the available attributes are invisible/benign. |
| 707 | const prev_nl_n = global_progress.written_newline_count; | 684 | if (!global_progress.need_clear) return; |
| 708 | if (prev_nl_n > 0) { | 685 | const handle = global_progress.terminal.handle; |
| 709 | const handle = global_progress.terminal.handle; | 686 | const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; |
| 710 | const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; | | |
| 711 | | 687 | |
| 712 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | 688 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; |
| 713 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { | 689 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { |
| 714 | return error.Unexpected; | 690 | return error.Unexpected; |
| 715 | } | 691 | } |
| 716 | const cursor_pos = console_info.dwCursorPosition; | 692 | var num_chars_written: windows.DWORD = undefined; |
| 717 | const expected_y = cursor_pos.Y - @as(i16, @intCast(prev_nl_n)); | 693 | if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, console_info.dwCursorPosition, &num_chars_written) == 0) { |
| 718 | var start_pos = windows.COORD{ .X = 0, .Y = expected_y }; | 694 | return error.Unexpected; |
| 719 | while (start_pos.Y >= 0) { | 695 | } |
| 720 | var wchar: [1]u16 = undefined; | 696 | } |
| 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 | } | | |
| 725 | | 697 | |
| 726 | if (wchar[0] == windows_api_start_marker) break; | 698 | fn windowsApiMoveToMarker(nl_n: usize) error{Unexpected}!void { |
| 727 | start_pos.Y -= 1; | 699 | const handle = global_progress.terminal.handle; |
| 728 | } else { | 700 | var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; |
| 729 | // If we couldn't find the marker, then just assume that no lines wrapped | 701 | if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { |
| 730 | start_pos = .{ .X = 0, .Y = expected_y }; | 702 | return error.Unexpected; |
| 731 | } | 703 | } |
| 732 | var num_chars_written: windows.DWORD = undefined; | 704 | const cursor_pos = console_info.dwCursorPosition; |
| 733 | if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, start_pos, &num_chars_written) == 0) { | 705 | const expected_y = cursor_pos.Y - @as(i16, @intCast(nl_n)); |
| 734 | return error.Unexpected; | 706 | var start_pos: windows.COORD = .{ .X = 0, .Y = expected_y }; |
| 735 | } | 707 | while (start_pos.Y >= 0) { |
| 736 | if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 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 | return error.Unexpected; | 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,7 +1035,7 @@ fn useSavedIpcData( |
| 1052 | return start_serialized_len + storage.len; | 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 | const serialized = serialize(serialized_buffer); | 1039 | const serialized = serialize(serialized_buffer); |
| 1057 | | 1040 | |
| 1058 | // Now we can analyze our copy of the graph without atomics, reconstructing | 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,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: | 1064 | // The strategy is, with every redraw: |
| 1082 | // move cursor to beginning of line, move cursor up N lines, erase to end of screen, write | 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 | var i: usize = 0; | 1069 | var i: usize = 0; |
| 1085 | const buf = global_progress.draw_buffer; | 1070 | const buf = global_progress.draw_buffer; |
| ... | @@ -1091,20 +1076,31 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 { | ... | @@ -1091,20 +1076,31 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 { |
| 1091 | | 1076 | |
| 1092 | switch (global_progress.terminal_mode) { | 1077 | switch (global_progress.terminal_mode) { |
| 1093 | .off => unreachable, | 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 | .windows_api => if (!is_windows) unreachable, | 1083 | .windows_api => if (!is_windows) unreachable, |
| 1096 | } | 1084 | } |
| 1097 | | 1085 | |
| 1098 | global_progress.accumulated_newline_count = 0; | | |
| 1099 | const root_node_index: Node.Index = @enumFromInt(0); | 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 | if (global_progress.terminal_mode == .ansi_escape_codes) { | 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 | buf[i..][0..finish_sync.len].* = finish_sync.*; | 1099 | buf[i..][0..finish_sync.len].* = finish_sync.*; |
| 1104 | i += finish_sync.len; | 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 | fn computePrefix( | 1106 | fn computePrefix( |
| ... | @@ -1138,20 +1134,23 @@ fn computePrefix( | ... | @@ -1138,20 +1134,23 @@ fn computePrefix( |
| 1138 | } | 1134 | } |
| 1139 | | 1135 | |
| 1140 | const line_upper_bound_len = @max(TreeSymbol.tee.maxByteLen(), TreeSymbol.langle.maxByteLen()) + | 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 | fn computeNode( | 1139 | fn computeNode( |
| 1144 | buf: []u8, | 1140 | buf: []u8, |
| 1145 | start_i: usize, | 1141 | start_i: usize, |
| | 1142 | start_nl_n: usize, |
| 1146 | serialized: Serialized, | 1143 | serialized: Serialized, |
| 1147 | children: []const Children, | 1144 | children: []const Children, |
| 1148 | node_index: Node.Index, | 1145 | node_index: Node.Index, |
| 1149 | ) usize { | 1146 | ) struct { usize, usize } { |
| 1150 | var i = start_i; | 1147 | var i = start_i; |
| | 1148 | var nl_n = start_nl_n; |
| | 1149 | |
| 1151 | i = computePrefix(buf, i, serialized, children, node_index); | 1150 | i = computePrefix(buf, i, serialized, children, node_index); |
| 1152 | | 1151 | |
| 1153 | if (i + line_upper_bound_len > buf.len) | 1152 | if (i + line_upper_bound_len > buf.len) |
| 1154 | return start_i; | 1153 | return .{ start_i, start_nl_n }; |
| 1155 | | 1154 | |
| 1156 | const storage = &serialized.storage[@intFromEnum(node_index)]; | 1155 | const storage = &serialized.storage[@intFromEnum(node_index)]; |
| 1157 | const estimated_total = storage.estimated_total_count; | 1156 | const estimated_total = storage.estimated_total_count; |
| ... | @@ -1186,34 +1185,33 @@ fn computeNode( | ... | @@ -1186,34 +1185,33 @@ fn computeNode( |
| 1186 | i = @min(global_progress.cols + start_i, i); | 1185 | i = @min(global_progress.cols + start_i, i); |
| 1187 | buf[i] = '\n'; | 1186 | buf[i] = '\n'; |
| 1188 | i += 1; | 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 | if (children[@intFromEnum(node_index)].child.unwrap()) |child| { | 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 | if (children[@intFromEnum(node_index)].sibling.unwrap()) |sibling| { | 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 | // The +2 here is so that the PS1 is not scrolled off the top of the terminal. | 1207 | // The +2 here is so that the PS1 is not scrolled off the top of the terminal. |
| 1209 | // one because we keep the cursor on the next line | 1208 | // one because we keep the cursor on the next line |
| 1210 | // one more to account for the PS1 | 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 | fn write(buf: []const u8) anyerror!void { | 1213 | fn write(buf: []const u8) anyerror!void { |
| 1215 | try global_progress.terminal.writeAll(buf); | 1214 | try global_progress.terminal.writeAll(buf); |
| 1216 | global_progress.written_newline_count = global_progress.accumulated_newline_count; | | |
| 1217 | } | 1215 | } |
| 1218 | | 1216 | |
| 1219 | var remaining_write_trash_bytes: usize = 0; | 1217 | var remaining_write_trash_bytes: usize = 0; |