|
|
@@ -57,6 +57,15 @@ namespace Usm.Cli {
|
|
|
*/
|
|
|
public class ProgressBar : Object {
|
|
|
|
|
|
+ private int pulse_position = 0;
|
|
|
+ private bool pulse_forward = true;
|
|
|
+ private uint pulse_timer = 0;
|
|
|
+
|
|
|
+ /** Whether the active phase bar is in indeterminate (pulsing) mode. */
|
|
|
+ public bool phase_is_indeterminate {
|
|
|
+ get { return phase_progress < 0.0f; }
|
|
|
+ }
|
|
|
+
|
|
|
private const string COLOUR_RESET = "\x1b[0m";
|
|
|
private const string COLOUR_RED = "\x1b[31m";
|
|
|
private const string COLOUR_GREEN = "\x1b[32m";
|
|
|
@@ -252,15 +261,39 @@ namespace Usm.Cli {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Starts an indeterminate (pulsing) phase bar — for operations
|
|
|
+ * with unknown duration (SPM queries, pre-Content-Length fetches).
|
|
|
+ * The bar shows a short segment sweeping back and forth across a
|
|
|
+ * dim track, with `--%` as the percentage. A GLib timeout drives
|
|
|
+ * the animation; {@link end_phase} stops it.
|
|
|
+ */
|
|
|
+ public void begin_indeterminate(string label) {
|
|
|
+ phase_label = label;
|
|
|
+ phase_progress = -1.0f;
|
|
|
+ phase_plain_key = "";
|
|
|
+ phase_plain_decile = -1;
|
|
|
+ pulse_forward = true;
|
|
|
+ pulse_position = 0;
|
|
|
+ if(interactive) {
|
|
|
+ draw_phase_line();
|
|
|
+ pulse_timer = Timeout.add(120, () => {
|
|
|
+ if(phase_progress < 0.0f) {
|
|
|
+ advance_pulse();
|
|
|
+ draw_phase_line();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Starts a plain single-line bar for a non-transaction phase —
|
|
|
- * strategising, package downloads — drawn without colours and
|
|
|
- * without the `[X/Y]` action counter:
|
|
|
- * `Resolving dependencies ███░░░░░ 0%`. A live transaction line
|
|
|
- * is never touched; phases run before (or between) transaction
|
|
|
- * displays.
|
|
|
+ * strategising, package downloads.
|
|
|
*/
|
|
|
public void begin_phase(string label) {
|
|
|
+ stop_pulse();
|
|
|
phase_label = label;
|
|
|
phase_progress = 0.0f;
|
|
|
phase_plain_key = "";
|
|
|
@@ -270,6 +303,34 @@ namespace Usm.Cli {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** Stops the indeterminate pulse timer if active. */
|
|
|
+ private void stop_pulse() {
|
|
|
+ if(pulse_timer > 0) {
|
|
|
+ Source.remove(pulse_timer);
|
|
|
+ pulse_timer = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void advance_pulse() {
|
|
|
+ var width = detect_terminal_width();
|
|
|
+ var text_zone = int.max(14, width / 3);
|
|
|
+ var bar_zone = width - text_zone;
|
|
|
+ var capacity = bar_zone - 6;
|
|
|
+ var segment = int.max(3, capacity / 8);
|
|
|
+ if(pulse_forward) {
|
|
|
+ pulse_position += 1;
|
|
|
+ if(pulse_position + segment >= capacity) {
|
|
|
+ pulse_forward = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ pulse_position -= 1;
|
|
|
+ if(pulse_position <= 0) {
|
|
|
+ pulse_forward = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Redraws the phase bar at {@link progress} (clamped to 0..1).
|
|
|
* The value is monotonic within a phase — a report below the
|
|
|
@@ -299,6 +360,7 @@ namespace Usm.Cli {
|
|
|
* the transaction bar). Harmless when no phase is active.
|
|
|
*/
|
|
|
public void end_phase() {
|
|
|
+ stop_pulse();
|
|
|
if(interactive && phase_live) {
|
|
|
stderr.printf("\r\033[K");
|
|
|
}
|
|
|
@@ -369,10 +431,16 @@ namespace Usm.Cli {
|
|
|
var text_zone = int.max(14, width / 3);
|
|
|
var colour = colour_for_task(current_task);
|
|
|
|
|
|
+ var text = zone_text(text_zone);
|
|
|
+ var padding = text_zone - text.char_count();
|
|
|
+ var padded = new StringBuilder(text);
|
|
|
+ for(var i = 0; i < padding; i++) {
|
|
|
+ padded.append(" ");
|
|
|
+ }
|
|
|
+
|
|
|
var cells = new StringBuilder();
|
|
|
cells.append(colour);
|
|
|
- cells.append(zone_text(text_zone));
|
|
|
- pad_to_zone(cells, text_zone);
|
|
|
+ cells.append(padded.str);
|
|
|
cells.append(COLOUR_RESET);
|
|
|
cells.append(bar_text(width - text_zone, overall, colour));
|
|
|
return cells.str;
|
|
|
@@ -387,10 +455,50 @@ namespace Usm.Cli {
|
|
|
var width = detect_terminal_width();
|
|
|
var text_zone = int.max(14, width / 3);
|
|
|
|
|
|
+ var text = ellipsise(phase_label ?? "", text_zone);
|
|
|
+ var padding = text_zone - text.char_count();
|
|
|
+ var padded = new StringBuilder(text);
|
|
|
+ for(var i = 0; i < padding; i++) {
|
|
|
+ padded.append(" ");
|
|
|
+ }
|
|
|
+
|
|
|
var cells = new StringBuilder();
|
|
|
- cells.append(ellipsise(phase_label ?? "", text_zone));
|
|
|
- pad_to_zone(cells, text_zone);
|
|
|
- cells.append(bar_text(width - text_zone, phase_progress, null));
|
|
|
+ cells.append(padded.str);
|
|
|
+ if(phase_progress < 0.0f) {
|
|
|
+ cells.append(indeterminate_bar_text(width - text_zone));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ cells.append(bar_text(width - text_zone, phase_progress, null));
|
|
|
+ }
|
|
|
+ return cells.str;
|
|
|
+ }
|
|
|
+
|
|
|
+ private string indeterminate_bar_text(int bar_zone) {
|
|
|
+ var capacity = bar_zone - 6;
|
|
|
+ var segment = int.max(3, capacity / 8);
|
|
|
+
|
|
|
+ const string DIM_BG = "\x1b[100m";
|
|
|
+ const string RESET = "\x1b[0m";
|
|
|
+ const string FILL = "\x1b[97m\x1b[107m";
|
|
|
+
|
|
|
+ var cells = new StringBuilder();
|
|
|
+ cells.append(" ");
|
|
|
+ cells.append(DIM_BG);
|
|
|
+ for(var i = 0; i < capacity; i++) {
|
|
|
+ if(i >= pulse_position && i < pulse_position + segment) {
|
|
|
+ cells.append(RESET);
|
|
|
+ cells.append(FILL);
|
|
|
+ cells.append(" ");
|
|
|
+ cells.append(RESET);
|
|
|
+ cells.append(DIM_BG);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ cells.append(" ");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cells.append(RESET);
|
|
|
+ cells.append(" ");
|
|
|
+ cells.append(" --%");
|
|
|
return cells.str;
|
|
|
}
|
|
|
|