| ... | @@ -25,6 +25,7 @@ redraw_event: std.Thread.ResetEvent, | ... | @@ -25,6 +25,7 @@ redraw_event: std.Thread.ResetEvent, |
| 25 | /// Accessed atomically. | 25 | /// Accessed atomically. |
| 26 | done: bool, | 26 | done: bool, |
| 27 | need_clear: bool, | 27 | need_clear: bool, |
| | 28 | status: Status, |
| 28 | | 29 | |
| 29 | refresh_rate_ns: u64, | 30 | refresh_rate_ns: u64, |
| 30 | initial_delay_ns: u64, | 31 | initial_delay_ns: u64, |
| ... | @@ -47,6 +48,22 @@ node_freelist: Freelist, | ... | @@ -47,6 +48,22 @@ node_freelist: Freelist, |
| 47 | /// value may at times temporarily exceed the node count. | 48 | /// value may at times temporarily exceed the node count. |
| 48 | node_end_index: u32, | 49 | node_end_index: u32, |
| 49 | | 50 | |
| | 51 | pub const Status = enum { |
| | 52 | /// Indicates the application is progressing towards completion of a task. |
| | 53 | /// Unless the application is interactive, this is the only status the |
| | 54 | /// program will ever have! |
| | 55 | working, |
| | 56 | /// The application has completed an operation, and is now waiting for user |
| | 57 | /// input rather than calling exit(0). |
| | 58 | success, |
| | 59 | /// The application encountered an error, and is now waiting for user input |
| | 60 | /// rather than calling exit(1). |
| | 61 | failure, |
| | 62 | /// The application encountered at least one error, but is still working on |
| | 63 | /// more tasks. |
| | 64 | failure_working, |
| | 65 | }; |
| | 66 | |
| 50 | const Freelist = packed struct(u32) { | 67 | const Freelist = packed struct(u32) { |
| 51 | head: Node.OptionalIndex, | 68 | head: Node.OptionalIndex, |
| 52 | /// Whenever `node_freelist` is added to, this generation is incremented | 69 | /// Whenever `node_freelist` is added to, this generation is incremented |
| ... | @@ -383,6 +400,7 @@ var global_progress: Progress = .{ | ... | @@ -383,6 +400,7 @@ var global_progress: Progress = .{ |
| 383 | .draw_buffer = undefined, | 400 | .draw_buffer = undefined, |
| 384 | .done = false, | 401 | .done = false, |
| 385 | .need_clear = false, | 402 | .need_clear = false, |
| | 403 | .status = .working, |
| 386 | | 404 | |
| 387 | .node_parents = &node_parents_buffer, | 405 | .node_parents = &node_parents_buffer, |
| 388 | .node_storage = &node_storage_buffer, | 406 | .node_storage = &node_storage_buffer, |
| ... | @@ -498,6 +516,11 @@ pub fn start(options: Options) Node { | ... | @@ -498,6 +516,11 @@ pub fn start(options: Options) Node { |
| 498 | return root_node; | 516 | return root_node; |
| 499 | } | 517 | } |
| 500 | | 518 | |
| | 519 | pub fn setStatus(new_status: Status) void { |
| | 520 | if (noop_impl) return; |
| | 521 | @atomicStore(Status, &global_progress.status, new_status, .monotonic); |
| | 522 | } |
| | 523 | |
| 501 | /// Returns whether a resize is needed to learn the terminal size. | 524 | /// Returns whether a resize is needed to learn the terminal size. |
| 502 | fn wait(timeout_ns: u64) bool { | 525 | fn wait(timeout_ns: u64) bool { |
| 503 | const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| | 526 | const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_| |
| ... | @@ -678,6 +701,14 @@ const save = "\x1b7"; | ... | @@ -678,6 +701,14 @@ const save = "\x1b7"; |
| 678 | const restore = "\x1b8"; | 701 | const restore = "\x1b8"; |
| 679 | const finish_sync = "\x1b[?2026l"; | 702 | const finish_sync = "\x1b[?2026l"; |
| 680 | | 703 | |
| | 704 | const progress_remove = "\x1b]9;4;0\x07"; |
| | 705 | const @"progress_normal {d}" = "\x1b]9;4;1;{d}\x07"; |
| | 706 | const @"progress_error {d}" = "\x1b]9;4;2;{d}\x07"; |
| | 707 | const progress_pulsing = "\x1b]9;4;3\x07"; |
| | 708 | const progress_pulsing_error = "\x1b]9;4;2\x07"; |
| | 709 | const progress_normal_100 = "\x1b]9;4;1;100\x07"; |
| | 710 | const progress_error_100 = "\x1b]9;4;2;100\x07"; |
| | 711 | |
| 681 | const TreeSymbol = enum { | 712 | const TreeSymbol = enum { |
| 682 | /// ├─ | 713 | /// ├─ |
| 683 | tee, | 714 | tee, |
| ... | @@ -760,7 +791,7 @@ fn clearWrittenWithEscapeCodes() anyerror!void { | ... | @@ -760,7 +791,7 @@ fn clearWrittenWithEscapeCodes() anyerror!void { |
| 760 | if (noop_impl or !global_progress.need_clear) return; | 791 | if (noop_impl or !global_progress.need_clear) return; |
| 761 | | 792 | |
| 762 | global_progress.need_clear = false; | 793 | global_progress.need_clear = false; |
| 763 | try write(clear); | 794 | try write(clear ++ progress_remove); |
| 764 | } | 795 | } |
| 765 | | 796 | |
| 766 | /// U+25BA or ► | 797 | /// U+25BA or ► |
| ... | @@ -1203,6 +1234,43 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { | ... | @@ -1203,6 +1234,43 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { |
| 1203 | i, const nl_n = computeNode(buf, i, 0, serialized, children, root_node_index); | 1234 | i, const nl_n = computeNode(buf, i, 0, serialized, children, root_node_index); |
| 1204 | | 1235 | |
| 1205 | if (global_progress.terminal_mode == .ansi_escape_codes) { | 1236 | if (global_progress.terminal_mode == .ansi_escape_codes) { |
| | 1237 | { |
| | 1238 | // Set progress state https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC |
| | 1239 | const root_storage = &serialized.storage[0]; |
| | 1240 | const storage = if (root_storage.name[0] != 0 or children[0].child == .none) root_storage else &serialized.storage[@intFromEnum(children[0].child)]; |
| | 1241 | const estimated_total = storage.estimated_total_count; |
| | 1242 | const completed_items = storage.completed_count; |
| | 1243 | const status = @atomicLoad(Status, &global_progress.status, .monotonic); |
| | 1244 | switch (status) { |
| | 1245 | .working => { |
| | 1246 | if (estimated_total == 0) { |
| | 1247 | buf[i..][0..progress_pulsing.len].* = progress_pulsing.*; |
| | 1248 | i += progress_pulsing.len; |
| | 1249 | } else { |
| | 1250 | const percent = completed_items * 100 / estimated_total; |
| | 1251 | i += (std.fmt.bufPrint(buf[i..], @"progress_normal {d}", .{percent}) catch &.{}).len; |
| | 1252 | } |
| | 1253 | }, |
| | 1254 | .success => { |
| | 1255 | buf[i..][0..progress_remove.len].* = progress_remove.*; |
| | 1256 | i += progress_remove.len; |
| | 1257 | }, |
| | 1258 | .failure => { |
| | 1259 | buf[i..][0..progress_error_100.len].* = progress_error_100.*; |
| | 1260 | i += progress_error_100.len; |
| | 1261 | }, |
| | 1262 | .failure_working => { |
| | 1263 | if (estimated_total == 0) { |
| | 1264 | buf[i..][0..progress_pulsing_error.len].* = progress_pulsing_error.*; |
| | 1265 | i += progress_pulsing_error.len; |
| | 1266 | } else { |
| | 1267 | const percent = completed_items * 100 / estimated_total; |
| | 1268 | i += (std.fmt.bufPrint(buf[i..], @"progress_error {d}", .{percent}) catch &.{}).len; |
| | 1269 | } |
| | 1270 | }, |
| | 1271 | } |
| | 1272 | } |
| | 1273 | |
| 1206 | if (nl_n > 0) { | 1274 | if (nl_n > 0) { |
| 1207 | buf[i] = '\r'; | 1275 | buf[i] = '\r'; |
| 1208 | i += 1; | 1276 | i += 1; |