Skip to content

Commit fc4b7c9

Browse files
authored
Merge pull request #24564 from ziglang/terminal-progress-bar
std.Progress: support progress bar escape codes
2 parents 66e49d9 + b22b9eb commit fc4b7c9

File tree

2 files changed

+75
-3
lines changed

2 files changed

+75
-3
lines changed

lib/compiler/build_runner.zig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,11 @@ fn runStepNames(
696696
.failures, .none => true,
697697
else => false,
698698
};
699-
if (failure_count == 0 and failures_only) {
700-
return run.cleanExit();
699+
if (failure_count == 0) {
700+
std.Progress.setStatus(.success);
701+
if (failures_only) return run.cleanExit();
702+
} else {
703+
std.Progress.setStatus(.failure);
701704
}
702705

703706
const ttyconf = run.ttyconf;
@@ -1149,6 +1152,7 @@ fn workerMakeOneStep(
11491152
} else |err| switch (err) {
11501153
error.MakeFailed => {
11511154
@atomicStore(Step.State, &s.state, .failure, .seq_cst);
1155+
std.Progress.setStatus(.failure_working);
11521156
break :handle_result;
11531157
},
11541158
error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .seq_cst),

lib/std/Progress.zig

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ redraw_event: std.Thread.ResetEvent,
2525
/// Accessed atomically.
2626
done: bool,
2727
need_clear: bool,
28+
status: Status,
2829

2930
refresh_rate_ns: u64,
3031
initial_delay_ns: u64,
@@ -47,6 +48,22 @@ node_freelist: Freelist,
4748
/// value may at times temporarily exceed the node count.
4849
node_end_index: u32,
4950

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+
5067
const Freelist = packed struct(u32) {
5168
head: Node.OptionalIndex,
5269
/// Whenever `node_freelist` is added to, this generation is incremented
@@ -383,6 +400,7 @@ var global_progress: Progress = .{
383400
.draw_buffer = undefined,
384401
.done = false,
385402
.need_clear = false,
403+
.status = .working,
386404

387405
.node_parents = &node_parents_buffer,
388406
.node_storage = &node_storage_buffer,
@@ -498,6 +516,11 @@ pub fn start(options: Options) Node {
498516
return root_node;
499517
}
500518

519+
pub fn setStatus(new_status: Status) void {
520+
if (noop_impl) return;
521+
@atomicStore(Status, &global_progress.status, new_status, .monotonic);
522+
}
523+
501524
/// Returns whether a resize is needed to learn the terminal size.
502525
fn wait(timeout_ns: u64) bool {
503526
const resize_flag = if (global_progress.redraw_event.timedWait(timeout_ns)) |_|
@@ -678,6 +701,14 @@ const save = "\x1b7";
678701
const restore = "\x1b8";
679702
const finish_sync = "\x1b[?2026l";
680703

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+
681712
const TreeSymbol = enum {
682713
/// ├─
683714
tee,
@@ -760,7 +791,7 @@ fn clearWrittenWithEscapeCodes() anyerror!void {
760791
if (noop_impl or !global_progress.need_clear) return;
761792

762793
global_progress.need_clear = false;
763-
try write(clear);
794+
try write(clear ++ progress_remove);
764795
}
765796

766797
/// U+25BA or ►
@@ -1203,6 +1234,43 @@ fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {
12031234
i, const nl_n = computeNode(buf, i, 0, serialized, children, root_node_index);
12041235

12051236
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+
12061274
if (nl_n > 0) {
12071275
buf[i] = '\r';
12081276
i += 1;

0 commit comments

Comments
 (0)