|
@@ -36,6 +36,15 @@ namespace Usm.Cli {
|
|
|
* — one per significant change, no ANSI — with the ASCII fallback
|
|
* — one per significant change, no ANSI — with the ASCII fallback
|
|
|
* characters in non-Unicode locales.
|
|
* characters in non-Unicode locales.
|
|
|
*
|
|
*
|
|
|
|
|
+ * Besides the transaction display, plain phase bars cover the
|
|
|
|
|
+ * stages around it — strategising, package downloads. A phase bar
|
|
|
|
|
+ * uses the same chunky cells and right-aligned percentage but no
|
|
|
|
|
+ * colours and no `[X/Y]` counter: {@link begin_phase} draws
|
|
|
|
|
+ * `Resolving dependencies ███░░░░░ 42%`, {@link update_phase}
|
|
|
|
|
+ * redraws it (monotonic within the phase), {@link phase_log}
|
|
|
|
|
+ * prints a completion line above the live bar and {@link end_phase}
|
|
|
|
|
+ * erases it for whatever displays next.
|
|
|
|
|
+ *
|
|
|
* Connect {@link on_transaction_progress} to
|
|
* Connect {@link on_transaction_progress} to
|
|
|
* {@link Usm.Transaction.progress_updated}:
|
|
* {@link Usm.Transaction.progress_updated}:
|
|
|
*
|
|
*
|
|
@@ -98,6 +107,12 @@ namespace Usm.Cli {
|
|
|
private string plain_key = "";
|
|
private string plain_key = "";
|
|
|
private int plain_decile = -1;
|
|
private int plain_decile = -1;
|
|
|
|
|
|
|
|
|
|
+ private string? phase_label = null;
|
|
|
|
|
+ private float phase_progress = 0.0f;
|
|
|
|
|
+ private bool phase_live = false;
|
|
|
|
|
+ private string phase_plain_key = "";
|
|
|
|
|
+ private int phase_plain_decile = -1;
|
|
|
|
|
+
|
|
|
construct {
|
|
construct {
|
|
|
// Charset probing needs the locale applied before it reflects
|
|
// Charset probing needs the locale applied before it reflects
|
|
|
// the environment; without this the C locale forces the ASCII
|
|
// the environment; without this the C locale forces the ASCII
|
|
@@ -225,6 +240,85 @@ namespace Usm.Cli {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 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.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void begin_phase(string label) {
|
|
|
|
|
+ phase_label = label;
|
|
|
|
|
+ phase_progress = 0.0f;
|
|
|
|
|
+ phase_plain_key = "";
|
|
|
|
|
+ phase_plain_decile = -1;
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ draw_phase_line();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Redraws the phase bar at {@link progress} (clamped to 0..1).
|
|
|
|
|
+ * The value is monotonic within a phase — a report below the
|
|
|
|
|
+ * current fill never redraws the bar emptier — so callers may
|
|
|
|
|
+ * feed raw byte or count fractions without policing ordering
|
|
|
|
|
+ * themselves. Ignored when no phase is active. Non-interactive
|
|
|
|
|
+ * streams print one line per decile change, mirroring
|
|
|
|
|
+ * {@link update}'s plain mode.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void update_phase(float progress) {
|
|
|
|
|
+ if(phase_label == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var clamped = progress < 0.0f ? 0.0f : progress > 1.0f ? 1.0f : progress;
|
|
|
|
|
+ phase_progress = clamped > phase_progress ? clamped : phase_progress;
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ draw_phase_line();
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ print_phase_plain();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Erases the phase bar without leaving a log line — the space
|
|
|
|
|
+ * is handed to whatever displays next (the transaction plan,
|
|
|
|
|
+ * the transaction bar). Harmless when no phase is active.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void end_phase() {
|
|
|
|
|
+ if(interactive && phase_live) {
|
|
|
|
|
+ stderr.printf("\r\033[K");
|
|
|
|
|
+ }
|
|
|
|
|
+ phase_live = false;
|
|
|
|
|
+ phase_label = null;
|
|
|
|
|
+ phase_progress = 0.0f;
|
|
|
|
|
+ phase_plain_key = "";
|
|
|
|
|
+ phase_plain_decile = -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Prints one completion line above the live phase bar —
|
|
|
|
|
+ * `✓ Downloaded invercargill-1.0.0.usmc (388 B)` — consuming
|
|
|
|
|
+ * the in-place line and redrawing the bar beneath it, so
|
|
|
|
|
+ * per-item logs scroll up like transaction action logs. Plain
|
|
|
|
|
+ * (no colours) like the phase bar itself.
|
|
|
|
|
+ */
|
|
|
|
|
+ public void phase_log(string message) {
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ if(phase_live) {
|
|
|
|
|
+ stderr.printf("\r\033[K");
|
|
|
|
|
+ }
|
|
|
|
|
+ stderr.printf("%s %s\n", success_mark, message);
|
|
|
|
|
+ if(phase_label != null) {
|
|
|
|
|
+ draw_phase_line();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ stderr.printf("%s %s\n", success_mark, message);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* The persistent log for the current action, in the past tense;
|
|
* The persistent log for the current action, in the past tense;
|
|
|
* printing it consumes the transient line, so the log scrolls
|
|
* printing it consumes the transient line, so the log scrolls
|
|
@@ -247,36 +341,69 @@ namespace Usm.Cli {
|
|
|
line_live = true;
|
|
line_live = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private void draw_phase_line() {
|
|
|
|
|
+ stderr.printf("\r\033[K%s", phase_line_text());
|
|
|
|
|
+ phase_live = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* One full terminal row: the action text space-padded to the
|
|
* One full terminal row: the action text space-padded to the
|
|
|
- * left third, then the bar zone filling the right two thirds —
|
|
|
|
|
- * gap, cells, gap and the overall percentage right-aligned in
|
|
|
|
|
- * a `100%`-wide field. The width is re-queried on every call
|
|
|
|
|
- * so a resized terminal re-proportions the layout immediately.
|
|
|
|
|
|
|
+ * left third, then the bar zone filling the right two thirds.
|
|
|
|
|
+ * The width is re-queried on every call so a resized terminal
|
|
|
|
|
+ * re-proportions the layout immediately.
|
|
|
*/
|
|
*/
|
|
|
private string line_text() {
|
|
private string line_text() {
|
|
|
var width = detect_terminal_width();
|
|
var width = detect_terminal_width();
|
|
|
var text_zone = int.max(14, width / 3);
|
|
var text_zone = int.max(14, width / 3);
|
|
|
- var bar_zone = width - text_zone;
|
|
|
|
|
|
|
|
|
|
var cells = new StringBuilder();
|
|
var cells = new StringBuilder();
|
|
|
cells.append(zone_text(text_zone));
|
|
cells.append(zone_text(text_zone));
|
|
|
|
|
+ pad_to_zone(cells, text_zone);
|
|
|
|
|
+ cells.append(bar_text(width - text_zone, overall));
|
|
|
|
|
+ return cells.str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The phase row: the phase label alone — no counter, no
|
|
|
|
|
+ * colours — ellipsised into the same left-third text zone,
|
|
|
|
|
+ * then the same bar zone at {@link phase_progress}.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string phase_line_text() {
|
|
|
|
|
+ var width = detect_terminal_width();
|
|
|
|
|
+ var text_zone = int.max(14, width / 3);
|
|
|
|
|
+
|
|
|
|
|
+ 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));
|
|
|
|
|
+ return cells.str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Space-pads {@link cells} out to exactly {@link text_zone} visible characters. */
|
|
|
|
|
+ private void pad_to_zone(StringBuilder cells, int text_zone) {
|
|
|
var padding = text_zone - cells.str.char_count();
|
|
var padding = text_zone - cells.str.char_count();
|
|
|
for(var i = 0; i < padding; i++) {
|
|
for(var i = 0; i < padding; i++) {
|
|
|
cells.append(" ");
|
|
cells.append(" ");
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // The percentage field is fixed at "100%" width, so the
|
|
|
|
|
- // cell count stays constant from 0% to 100%
|
|
|
|
|
- var percent = (int)(overall * 100);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The bar zone proper — gap, `█`/`░` cells (`[#### ]` in the
|
|
|
|
|
+ * ASCII fallback), gap and the fraction's percentage
|
|
|
|
|
+ * right-aligned in a `100%`-wide field. The fixed percentage
|
|
|
|
|
+ * field keeps the cell count constant from 0% to 100%, and the
|
|
|
|
|
+ * fill uses + 0.5 rounding in plain arithmetic because the CLI
|
|
|
|
|
+ * target does not link libm.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string bar_text(int bar_zone, float fraction) {
|
|
|
|
|
+ var percent = (int)(fraction * 100);
|
|
|
var capacity = bar_zone - 6 - (unicode ? 0 : 2);
|
|
var capacity = bar_zone - 6 - (unicode ? 0 : 2);
|
|
|
- // + 0.5 rounding in plain arithmetic: the CLI target does not
|
|
|
|
|
- // link libm, so Math.roundf is unavailable here
|
|
|
|
|
- var filled = (int)(overall * capacity + 0.5f);
|
|
|
|
|
|
|
+ var filled = (int)(fraction * capacity + 0.5f);
|
|
|
if(filled > capacity) {
|
|
if(filled > capacity) {
|
|
|
filled = capacity;
|
|
filled = capacity;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ var cells = new StringBuilder();
|
|
|
cells.append(" ");
|
|
cells.append(" ");
|
|
|
if(!unicode) {
|
|
if(!unicode) {
|
|
|
cells.append("[");
|
|
cells.append("[");
|
|
@@ -323,6 +450,18 @@ namespace Usm.Cli {
|
|
|
stderr.printf("%s\n", line_text());
|
|
stderr.printf("%s\n", line_text());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** Plain-mode stream for {@link update_phase}: one line per phase or decile change. */
|
|
|
|
|
+ private void print_phase_plain() {
|
|
|
|
|
+ var key = phase_label ?? "";
|
|
|
|
|
+ var decile = (int)(phase_progress * 10);
|
|
|
|
|
+ if(key == phase_plain_key && decile == phase_plain_decile) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ phase_plain_key = key;
|
|
|
|
|
+ phase_plain_decile = decile;
|
|
|
|
|
+ stderr.printf("%s\n", phase_line_text());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Truncates {@link label} to {@link allowed} visible characters
|
|
* Truncates {@link label} to {@link allowed} visible characters
|
|
|
* with an ellipsis, char-aligned so a multi-byte name is never
|
|
* with an ellipsis, char-aligned so a multi-byte name is never
|