|
@@ -1,28 +1,40 @@
|
|
|
namespace Usm.Cli {
|
|
namespace Usm.Cli {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Two-line terminal progress display for a running
|
|
|
|
|
- * {@link Usm.Transaction}. The bottom line is a bar for the ENTIRE
|
|
|
|
|
- * transaction — `(task + task progress) / total tasks` taken from
|
|
|
|
|
- * {@link Usm.Transaction.progress_updated} — and the line above it
|
|
|
|
|
- * carries the current action with its own percentage. When a package
|
|
|
|
|
- * finishes, its action line is erased and replaced by the completion
|
|
|
|
|
- * log, which scrolls up as the next action takes the slot:
|
|
|
|
|
|
|
+ * Single-line, in-place progress display for a running
|
|
|
|
|
+ * {@link Usm.Transaction}:
|
|
|
*
|
|
*
|
|
|
* ```
|
|
* ```
|
|
|
- * ✓ falpha-1.0.0 installed
|
|
|
|
|
- * Building fgamma-1.0.0 (67%)
|
|
|
|
|
- * [████████████████░░░░░░░░░░░░░░░░░░░] 42%
|
|
|
|
|
|
|
+ * → [3/14] Installing invercargill ████████████████░░░░░░░░░░░ 42%
|
|
|
* ```
|
|
* ```
|
|
|
*
|
|
*
|
|
|
- * The bar's cell count is picked once, at construction, from the
|
|
|
|
|
- * terminal width and never changes for the bar's lifetime (a resize
|
|
|
|
|
- * mid-transaction is not re-fetched). Colours follow the current
|
|
|
|
|
- * {@link Usm.TransactionTask} (green unpack/install, blue build,
|
|
|
|
|
|
|
+ * The line splits into two fixed zones: the leftmost third of the
|
|
|
|
|
+ * terminal carries the text — arrow, action counter and
|
|
|
|
|
+ * present-tense action label, ellipsised and space-padded to
|
|
|
|
|
+ * exactly the zone width — and the right two thirds hold the bar:
|
|
|
|
|
+ * a gap, the `█`/`░` cells (`[#### ]` with brackets in the
|
|
|
|
|
+ * ASCII fallback), a second gap and the OVERALL transaction
|
|
|
|
|
+ * percentage, right-aligned in a `100%`-wide field so the cell
|
|
|
|
|
+ * count never changes. Bar fill and trailing number both track
|
|
|
|
|
+ * `(task + task progress) / total tasks` from
|
|
|
|
|
+ * {@link Usm.Transaction.progress_updated}; the current action's
|
|
|
|
|
+ * own percentage is not shown. The width is re-queried on every
|
|
|
|
|
+ * draw, so a resized terminal re-proportions the zones on the
|
|
|
|
|
+ * next report.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Each completed action leaves a persistent log line that scrolls
|
|
|
|
|
+ * up — `✓ [3/14] Installed invercargill` (past tense, task
|
|
|
|
|
+ * colour) — by consuming the in-place line; repeated 100% reports
|
|
|
|
|
+ * for one action never duplicate it. A successful close prints
|
|
|
|
|
+ * `✓ Transaction completed` in green; failures print
|
|
|
|
|
+ * `✗ [3/14] Failed invercargill` in red. Colours per
|
|
|
|
|
+ * {@link Usm.TransactionTask}: green unpack/install, blue build,
|
|
|
* magenta test, yellow remove/rebuild/clean-up, cyan strategy, red
|
|
* magenta test, yellow remove/rebuild/clean-up, cyan strategy, red
|
|
|
- * failure). When stderr is not a terminal the display degrades to
|
|
|
|
|
- * plain `[42%] Installing invercargill (2/5)` lines on 10% steps
|
|
|
|
|
- * with no ANSI codes.
|
|
|
|
|
|
|
+ * failure.
|
|
|
|
|
+ *
|
|
|
|
|
+ * When stderr is not a terminal the same lines print as plain text
|
|
|
|
|
+ * — one per significant change, no ANSI — with the ASCII fallback
|
|
|
|
|
+ * characters in non-Unicode locales.
|
|
|
*
|
|
*
|
|
|
* Connect {@link on_transaction_progress} to
|
|
* Connect {@link on_transaction_progress} to
|
|
|
* {@link Usm.Transaction.progress_updated}:
|
|
* {@link Usm.Transaction.progress_updated}:
|
|
@@ -49,13 +61,10 @@ namespace Usm.Cli {
|
|
|
private const ulong TIOCGWINSZ = 0x5413;
|
|
private const ulong TIOCGWINSZ = 0x5413;
|
|
|
|
|
|
|
|
private bool interactive;
|
|
private bool interactive;
|
|
|
- private int width;
|
|
|
|
|
-
|
|
|
|
|
- /** Bar cell count, fixed for the bar's lifetime so the bar never grows or shrinks. */
|
|
|
|
|
- private int bar_capacity;
|
|
|
|
|
|
|
|
|
|
private string bar_filled_char;
|
|
private string bar_filled_char;
|
|
|
private string bar_empty_char;
|
|
private string bar_empty_char;
|
|
|
|
|
+ private string action_mark;
|
|
|
private string success_mark;
|
|
private string success_mark;
|
|
|
private string failure_mark;
|
|
private string failure_mark;
|
|
|
private bool unicode;
|
|
private bool unicode;
|
|
@@ -66,11 +75,6 @@ namespace Usm.Cli {
|
|
|
private int current_index;
|
|
private int current_index;
|
|
|
private int current_total;
|
|
private int current_total;
|
|
|
|
|
|
|
|
- /** A package's completion log fires exactly once, at CLEANING_UP's final 1.0 report. */
|
|
|
|
|
- private bool completion_printed;
|
|
|
|
|
- private bool current_was_removal;
|
|
|
|
|
- private bool current_was_rebuild;
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* Whole-transaction fraction: the running maximum of
|
|
* Whole-transaction fraction: the running maximum of
|
|
|
* `(task + task progress) / total tasks`. Task weighting is the
|
|
* `(task + task progress) / total tasks`. Task weighting is the
|
|
@@ -80,9 +84,16 @@ namespace Usm.Cli {
|
|
|
*/
|
|
*/
|
|
|
private float overall = 0.0f;
|
|
private float overall = 0.0f;
|
|
|
|
|
|
|
|
- /** Managed-region state: the bar row exists once drawn; the action row above it comes and goes. */
|
|
|
|
|
- private bool bar_started;
|
|
|
|
|
- private bool action_live;
|
|
|
|
|
|
|
+ /** Whether the transient in-place line is currently on screen. */
|
|
|
|
|
+ private bool line_live;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Action counter of the last logged completion. Actions log at
|
|
|
|
|
+ * most once however many 100% reports they emit (installs fire
|
|
|
|
|
+ * several); the counter is monotonic within a transaction, so
|
|
|
|
|
+ * `action > last_logged_action` is a sufficient duplicate guard.
|
|
|
|
|
+ */
|
|
|
|
|
+ private int last_logged_action = -1;
|
|
|
|
|
|
|
|
private string plain_key = "";
|
|
private string plain_key = "";
|
|
|
private int plain_decile = -1;
|
|
private int plain_decile = -1;
|
|
@@ -90,15 +101,14 @@ namespace Usm.Cli {
|
|
|
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
|
|
|
- // bar/mark fallbacks even on UTF-8 terminals
|
|
|
|
|
|
|
+ // fallbacks even on UTF-8 terminals
|
|
|
Intl.setlocale(LocaleCategory.ALL, "");
|
|
Intl.setlocale(LocaleCategory.ALL, "");
|
|
|
interactive = Posix.isatty(Posix.STDERR_FILENO);
|
|
interactive = Posix.isatty(Posix.STDERR_FILENO);
|
|
|
- width = detect_terminal_width();
|
|
|
|
|
- bar_capacity = int.max(10, int.min(40, width - 40));
|
|
|
|
|
unowned string charset;
|
|
unowned string charset;
|
|
|
unicode = get_charset(out charset);
|
|
unicode = get_charset(out charset);
|
|
|
bar_filled_char = unicode ? "█" : "#";
|
|
bar_filled_char = unicode ? "█" : "#";
|
|
|
- bar_empty_char = unicode ? "░" : "-";
|
|
|
|
|
|
|
+ bar_empty_char = unicode ? "░" : " ";
|
|
|
|
|
+ action_mark = unicode ? "→" : ">";
|
|
|
success_mark = unicode ? "✓" : "+";
|
|
success_mark = unicode ? "✓" : "+";
|
|
|
failure_mark = unicode ? "✗" : "x";
|
|
failure_mark = unicode ? "✗" : "x";
|
|
|
}
|
|
}
|
|
@@ -112,208 +122,222 @@ namespace Usm.Cli {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Records one progress report and redraws the two-line display
|
|
|
|
|
- * with the cursor left at the end of the bar row. A package whose
|
|
|
|
|
- * CLEANING_UP reaches 100% — its last report — instead has its
|
|
|
|
|
- * action line erased and replaced by the completion log (see
|
|
|
|
|
- * {@link complete_line}).
|
|
|
|
|
|
|
+ * Records one progress report and redraws the in-place line. A
|
|
|
|
|
+ * report that moves the action counter on, or the current
|
|
|
|
|
+ * action's first 100%, retires the current action: it consumes
|
|
|
|
|
+ * the in-place line and leaves the persistent
|
|
|
|
|
+ * `✓ [n/m] <past tense> <package>` log above it — once only,
|
|
|
|
|
+ * however many further 100% reports the action emits.
|
|
|
*/
|
|
*/
|
|
|
public void update(TransactionTask task, float progress, string? package_name, int index, int total) {
|
|
public void update(TransactionTask task, float progress, string? package_name, int index, int total) {
|
|
|
- var completes_now = task == TransactionTask.CLEANING_UP && progress >= 1.0f
|
|
|
|
|
- && package_name != null && package_name == current_package && !completion_printed;
|
|
|
|
|
- var outcome = current_was_removal ? TransactionTask.REMOVING
|
|
|
|
|
- : current_was_rebuild ? TransactionTask.REBUILDING : TransactionTask.INSTALLING;
|
|
|
|
|
-
|
|
|
|
|
- if(package_name != current_package) {
|
|
|
|
|
- current_package = package_name;
|
|
|
|
|
- completion_printed = false;
|
|
|
|
|
- current_was_removal = false;
|
|
|
|
|
- current_was_rebuild = false;
|
|
|
|
|
- }
|
|
|
|
|
- if(task == TransactionTask.REMOVING) {
|
|
|
|
|
- current_was_removal = true;
|
|
|
|
|
- }
|
|
|
|
|
- if(task == TransactionTask.REBUILDING) {
|
|
|
|
|
- current_was_rebuild = true;
|
|
|
|
|
|
|
+ var same_action = index == current_index;
|
|
|
|
|
+ var retire = line_live && current_index > last_logged_action
|
|
|
|
|
+ && (index > current_index || (same_action && progress >= 1.0f));
|
|
|
|
|
+
|
|
|
|
|
+ if(retire) {
|
|
|
|
|
+ if(!interactive && same_action) {
|
|
|
|
|
+ print_plain();
|
|
|
|
|
+ }
|
|
|
|
|
+ print_action_log();
|
|
|
|
|
+ last_logged_action = current_index;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
current_task = task;
|
|
current_task = task;
|
|
|
|
|
+ current_package = package_name;
|
|
|
current_progress = progress < 0.0f ? 0.0f : progress > 1.0f ? 1.0f : progress;
|
|
current_progress = progress < 0.0f ? 0.0f : progress > 1.0f ? 1.0f : progress;
|
|
|
current_index = index;
|
|
current_index = index;
|
|
|
current_total = total;
|
|
current_total = total;
|
|
|
- var reported = current_total > 0 ? ((float)current_index + current_progress) / (float)current_total : 0.0f;
|
|
|
|
|
|
|
+ var reported = total > 0 ? ((float)index + current_progress) / (float)total : 0.0f;
|
|
|
overall = reported > overall ? reported : overall;
|
|
overall = reported > overall ? reported : overall;
|
|
|
|
|
|
|
|
- if(completes_now) {
|
|
|
|
|
- if(!interactive) {
|
|
|
|
|
|
|
+ // A retired action's own 100% report does not redraw — its
|
|
|
|
|
+ // log just consumed the line; the next action draws fresh
|
|
|
|
|
+ if(!(retire && same_action)) {
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ draw_line();
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
print_plain();
|
|
print_plain();
|
|
|
}
|
|
}
|
|
|
- print_completion(package_name, outcome, true);
|
|
|
|
|
- completion_printed = true;
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if(interactive) {
|
|
|
|
|
- draw_region();
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- print_plain();
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Replaces the current action line with a completion log —
|
|
|
|
|
- * `✓ falpha-1.0.0 installed` in the task colour, or
|
|
|
|
|
- * `✗ falpha-1.0.0 failed` in red — which scrolls up as history,
|
|
|
|
|
- * then redraws the bar underneath it.
|
|
|
|
|
|
|
+ * Prints a completion log for an explicit ({@link package_name},
|
|
|
|
|
+ * {@link task}) pair — the same `✓ [n/m] <past tense> <package>`
|
|
|
|
|
+ * line actions leave automatically, or `✗ [n/m] Failed <package>`
|
|
|
|
|
+ * in red when {@link success} is false.
|
|
|
*/
|
|
*/
|
|
|
public void complete_line(string package_name, TransactionTask task, bool success) {
|
|
public void complete_line(string package_name, TransactionTask task, bool success) {
|
|
|
- print_completion(package_name, task, success);
|
|
|
|
|
|
|
+ var counter = current_total > 0 ? @"[$(current_index + 1)/$current_total] " : "";
|
|
|
|
|
+ var outcome = success ? past_tense(task) : "Failed";
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ stderr.printf("\r\033[K%s%s %s%s %s%s\n",
|
|
|
|
|
+ success ? colour_for_task(task) : COLOUR_RED,
|
|
|
|
|
+ success ? success_mark : failure_mark, counter, outcome, package_name, COLOUR_RESET);
|
|
|
|
|
+ line_live = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ stderr.printf("%s %s%s %s\n", success ? success_mark : failure_mark, counter, outcome, package_name);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Marks the in-flight package failed (a thrown
|
|
|
|
|
- * {@link Usm.TransactionError} aborts the run): erases the whole
|
|
|
|
|
- * managed region and prints `✗ <package> failed` in red — no
|
|
|
|
|
- * redraw, the transaction is over. Harmless when nothing was
|
|
|
|
|
- * drawn yet.
|
|
|
|
|
|
|
+ * Marks the in-flight action failed (a thrown
|
|
|
|
|
+ * {@link Usm.TransactionError} aborts the run): erases the
|
|
|
|
|
+ * in-place line and prints `✗ [n/m] Failed <package>` in red.
|
|
|
|
|
+ * Harmless when nothing was drawn yet.
|
|
|
*/
|
|
*/
|
|
|
public void fail() {
|
|
public void fail() {
|
|
|
- if(!interactive) {
|
|
|
|
|
- if(current_package != null && !completion_printed) {
|
|
|
|
|
- completion_printed = true;
|
|
|
|
|
- stderr.printf("%s %s failed\n", failure_mark, current_package);
|
|
|
|
|
|
|
+ var counter = current_total > 0 ? @"[$(current_index + 1)/$current_total] " : "";
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ if(line_live) {
|
|
|
|
|
+ stderr.printf("\r\033[K");
|
|
|
}
|
|
}
|
|
|
- return;
|
|
|
|
|
|
|
+ stderr.printf("%s%s %sFailed %s%s\n", COLOUR_RED, failure_mark, counter, current_package ?? "transaction", COLOUR_RESET);
|
|
|
|
|
+ line_live = false;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if(bar_started) {
|
|
|
|
|
- collapse_region();
|
|
|
|
|
- }
|
|
|
|
|
- if(current_package != null && !completion_printed) {
|
|
|
|
|
- completion_printed = true;
|
|
|
|
|
- stderr.printf("%s%s %s failed%s\r\n", COLOUR_RED, failure_mark, current_package, COLOUR_RESET);
|
|
|
|
|
|
|
+ else {
|
|
|
|
|
+ stderr.printf("%s %sFailed %s\n", failure_mark, counter, current_package ?? "transaction");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Erases the managed region and prints the closing summary line,
|
|
|
|
|
- * leaving the cursor on a fresh row for the caller's own totals.
|
|
|
|
|
|
|
+ * Closes the display after a successful transaction: an action
|
|
|
|
|
+ * that never reported 100% still leaves its log, the transient
|
|
|
|
|
+ * line is erased, and `✓ Transaction completed` prints in
|
|
|
|
|
+ * green, scrolling up with the other logs.
|
|
|
*/
|
|
*/
|
|
|
public void finish() {
|
|
public void finish() {
|
|
|
- if(!interactive || !bar_started) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ if(line_live && current_index > last_logged_action) {
|
|
|
|
|
+ print_action_log();
|
|
|
|
|
+ last_logged_action = current_index;
|
|
|
|
|
+ }
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ if(line_live) {
|
|
|
|
|
+ stderr.printf("\r\033[K");
|
|
|
|
|
+ }
|
|
|
|
|
+ line_live = false;
|
|
|
|
|
+ stderr.printf("%s%s Transaction completed%s\n", COLOUR_GREEN, success_mark, COLOUR_RESET);
|
|
|
|
|
+ }
|
|
|
|
|
+ else {
|
|
|
|
|
+ stderr.printf("%s Transaction completed\n", success_mark);
|
|
|
}
|
|
}
|
|
|
- collapse_region();
|
|
|
|
|
- stderr.printf("%s%s Transaction completed%s\r\n", COLOUR_GREEN, success_mark, COLOUR_RESET);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Redraws both managed rows. The cursor starts (and ends) on the
|
|
|
|
|
- * bar row; the action row is one `\033[1A` above it:
|
|
|
|
|
- * up, erase, action; down, erase, bar. When the action row was
|
|
|
|
|
- * consumed by a completion log, `\r\n` first opens a fresh row
|
|
|
|
|
- * below the bar so the bar can move onto it while the action
|
|
|
|
|
- * text takes the old bar row.
|
|
|
|
|
|
|
+ * The persistent log for the current action, in the past tense;
|
|
|
|
|
+ * printing it consumes the transient line, so the log scrolls
|
|
|
|
|
+ * up as the next action redraws below.
|
|
|
*/
|
|
*/
|
|
|
- private void draw_region() {
|
|
|
|
|
- var action = colourised_action_line();
|
|
|
|
|
- var bar = bar_line();
|
|
|
|
|
- if(!bar_started) {
|
|
|
|
|
- stderr.printf("%s\r\n\033[K%s", action, bar);
|
|
|
|
|
- bar_started = true;
|
|
|
|
|
- action_live = true;
|
|
|
|
|
- }
|
|
|
|
|
- else if(!action_live) {
|
|
|
|
|
- stderr.printf("\r\n\033[1A\033[K%s\r\033[1B\033[K%s", action, bar);
|
|
|
|
|
- action_live = true;
|
|
|
|
|
|
|
+ private void print_action_log() {
|
|
|
|
|
+ var counter = @"[$(current_index + 1)/$current_total]";
|
|
|
|
|
+ if(interactive) {
|
|
|
|
|
+ stderr.printf("\r\033[K%s%s %s %s %s%s\n", colour_for_task(current_task), success_mark,
|
|
|
|
|
+ counter, past_tense(current_task), current_package ?? "transaction", COLOUR_RESET);
|
|
|
|
|
+ line_live = false;
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- stderr.printf("\r\033[1A\033[K%s\r\033[1B\033[K%s", action, bar);
|
|
|
|
|
|
|
+ stderr.printf("%s %s %s %s\n", success_mark, counter, past_tense(current_task), current_package ?? "transaction");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void print_completion(string package_name, TransactionTask task, bool success) {
|
|
|
|
|
- var outcome = success ? past_tense(task) : "failed";
|
|
|
|
|
- var mark = success ? success_mark : failure_mark;
|
|
|
|
|
- var colour = success ? colour_for_task(task) : COLOUR_RED;
|
|
|
|
|
- var log = @"$colour$(fit(@"$mark $package_name $outcome"))$COLOUR_RESET";
|
|
|
|
|
|
|
+ private void draw_line() {
|
|
|
|
|
+ stderr.printf("\r\033[K%s%s%s", colour_for_task(current_task), line_text(), COLOUR_RESET);
|
|
|
|
|
+ line_live = true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if(!interactive) {
|
|
|
|
|
- stderr.printf("%s %s %s\n", mark, package_name, outcome);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 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.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string line_text() {
|
|
|
|
|
+ var width = detect_terminal_width();
|
|
|
|
|
+ var text_zone = int.max(14, width / 3);
|
|
|
|
|
+ var bar_zone = width - text_zone;
|
|
|
|
|
|
|
|
- // The log takes the row the action line (or, without one, the
|
|
|
|
|
- // bar itself) occupied; the bar redraws below it, so the log
|
|
|
|
|
- // stays above the region as scrolling history
|
|
|
|
|
- if(action_live) {
|
|
|
|
|
- stderr.printf("\r\033[1A\033[K%s\r\n\033[K%s", log, bar_line());
|
|
|
|
|
- action_live = false;
|
|
|
|
|
- }
|
|
|
|
|
- else if(bar_started) {
|
|
|
|
|
- stderr.printf("\r\n\033[1A\033[K%s\r\n\033[K%s", log, bar_line());
|
|
|
|
|
- }
|
|
|
|
|
- else {
|
|
|
|
|
- stderr.printf("%s\r\n", log);
|
|
|
|
|
|
|
+ var cells = new StringBuilder();
|
|
|
|
|
+ cells.append(zone_text(text_zone));
|
|
|
|
|
+ var padding = text_zone - cells.str.char_count();
|
|
|
|
|
+ for(var i = 0; i < padding; i++) {
|
|
|
|
|
+ cells.append(" ");
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- /** Erases the bar row and, when live, the action row above it, leaving the cursor at the action row's column zero. */
|
|
|
|
|
- private void collapse_region() {
|
|
|
|
|
- stderr.printf("\r\033[K");
|
|
|
|
|
- if(action_live) {
|
|
|
|
|
- stderr.printf("\r\033[1A\033[K");
|
|
|
|
|
- action_live = false;
|
|
|
|
|
|
|
+ // The percentage field is fixed at "100%" width, so the
|
|
|
|
|
+ // cell count stays constant from 0% to 100%
|
|
|
|
|
+ var percent = (int)(overall * 100);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ if(filled > capacity) {
|
|
|
|
|
+ filled = capacity;
|
|
|
}
|
|
}
|
|
|
- bar_started = false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private string colourised_action_line() {
|
|
|
|
|
- var colour = colour_for_task(current_task);
|
|
|
|
|
- var percent = (int)(current_progress * 100);
|
|
|
|
|
- return @"$colour $(fit(@"$(label_for(current_task, current_package)) ($percent%)"))$COLOUR_RESET";
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- private string bar_line() {
|
|
|
|
|
- var colour = colour_for_task(current_task);
|
|
|
|
|
- var percent = (int)(overall * 100);
|
|
|
|
|
- var filled = (int)(overall * bar_capacity);
|
|
|
|
|
- var cells = new StringBuilder();
|
|
|
|
|
|
|
+ cells.append(" ");
|
|
|
|
|
+ if(!unicode) {
|
|
|
|
|
+ cells.append("[");
|
|
|
|
|
+ }
|
|
|
for(var i = 0; i < filled; i++) {
|
|
for(var i = 0; i < filled; i++) {
|
|
|
cells.append(bar_filled_char);
|
|
cells.append(bar_filled_char);
|
|
|
}
|
|
}
|
|
|
- for(var i = filled; i < bar_capacity; i++) {
|
|
|
|
|
|
|
+ for(var i = filled; i < capacity; i++) {
|
|
|
cells.append(bar_empty_char);
|
|
cells.append(bar_empty_char);
|
|
|
}
|
|
}
|
|
|
- return @"$colour[$(cells.str)] $percent%$COLOUR_RESET";
|
|
|
|
|
|
|
+ if(!unicode) {
|
|
|
|
|
+ cells.append("]");
|
|
|
|
|
+ }
|
|
|
|
|
+ cells.append(" ");
|
|
|
|
|
+ cells.append("%3d%%".printf(percent));
|
|
|
|
|
+ return cells.str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * `→ [3/14] Installing pkg`: the action label is ellipsised
|
|
|
|
|
+ * into whatever the text zone leaves it; when even the arrow
|
|
|
|
|
+ * and counter overflow a very narrow zone the whole text is
|
|
|
|
|
+ * ellipsised instead.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string zone_text(int text_zone) {
|
|
|
|
|
+ var counter = @"[$(current_index + 1)/$current_total]";
|
|
|
|
|
+ var head = @"$action_mark $counter ";
|
|
|
|
|
+ var label = label_for(current_task, current_package);
|
|
|
|
|
+ var allowed = text_zone - head.char_count();
|
|
|
|
|
+ if(allowed >= 4) {
|
|
|
|
|
+ return @"$head$(ellipsise(label, allowed))";
|
|
|
|
|
+ }
|
|
|
|
|
+ return ellipsise(@"$head$label", text_zone);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void print_plain() {
|
|
private void print_plain() {
|
|
|
- var key = @"$current_package|$(current_task)";
|
|
|
|
|
|
|
+ var key = @"$current_package|$(current_task)|$current_index";
|
|
|
var decile = (int)(current_progress * 10);
|
|
var decile = (int)(current_progress * 10);
|
|
|
if(key == plain_key && decile == plain_decile) {
|
|
if(key == plain_key && decile == plain_decile) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
plain_key = key;
|
|
plain_key = key;
|
|
|
plain_decile = decile;
|
|
plain_decile = decile;
|
|
|
-
|
|
|
|
|
- var counts = current_total > 0 ? @" ($(current_index + 1)/$current_total)" : "";
|
|
|
|
|
- stderr.printf("[%d%%] %s%s\n", (int)(current_progress * 100), label_for(current_task, current_package), counts);
|
|
|
|
|
|
|
+ stderr.printf("%s\n", line_text());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Truncates {@link text} to one terminal row — a wrapped action
|
|
|
|
|
- * or log line would break the two-line geometry. Applied to
|
|
|
|
|
- * plain text only, before colour wrapping, so the budget counts
|
|
|
|
|
- * visible characters and never cuts inside an escape sequence.
|
|
|
|
|
|
|
+ * Truncates {@link label} to {@link allowed} visible characters
|
|
|
|
|
+ * with an ellipsis, char-aligned so a multi-byte name is never
|
|
|
|
|
+ * cut mid-codepoint.
|
|
|
*/
|
|
*/
|
|
|
- private string fit(string text) {
|
|
|
|
|
- var limit = int.max(8, width - 1);
|
|
|
|
|
- if(text.char_count() <= limit) {
|
|
|
|
|
- return text;
|
|
|
|
|
|
|
+ private string ellipsise(string label, int allowed) {
|
|
|
|
|
+ if(label.char_count() <= allowed) {
|
|
|
|
|
+ return label;
|
|
|
}
|
|
}
|
|
|
- return text.substring(0, text.index_of_nth_char(limit - 1)) + (unicode ? "…" : "~");
|
|
|
|
|
|
|
+ var marker = unicode ? "…" : "...";
|
|
|
|
|
+ var cut = allowed - marker.char_count();
|
|
|
|
|
+ if(cut <= 0) {
|
|
|
|
|
+ return marker;
|
|
|
|
|
+ }
|
|
|
|
|
+ return label.substring(0, label.index_of_nth_char(cut)) + marker;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private string label_for(TransactionTask task, string? package_name) {
|
|
private string label_for(TransactionTask task, string? package_name) {
|
|
@@ -347,20 +371,22 @@ namespace Usm.Cli {
|
|
|
|
|
|
|
|
private string past_tense(TransactionTask task) {
|
|
private string past_tense(TransactionTask task) {
|
|
|
switch(task) {
|
|
switch(task) {
|
|
|
- case TransactionTask.INSTALLING:
|
|
|
|
|
- return "installed";
|
|
|
|
|
- case TransactionTask.REMOVING:
|
|
|
|
|
- return "removed";
|
|
|
|
|
- case TransactionTask.REBUILDING:
|
|
|
|
|
- return "rebuilt";
|
|
|
|
|
|
|
+ case TransactionTask.STRATEGISING:
|
|
|
|
|
+ return "Prepared";
|
|
|
case TransactionTask.UNPACKING:
|
|
case TransactionTask.UNPACKING:
|
|
|
- return "unpacked";
|
|
|
|
|
|
|
+ return "Unpacked";
|
|
|
case TransactionTask.BUILDING:
|
|
case TransactionTask.BUILDING:
|
|
|
- return "built";
|
|
|
|
|
|
|
+ return "Built";
|
|
|
case TransactionTask.TESTING:
|
|
case TransactionTask.TESTING:
|
|
|
- return "tested";
|
|
|
|
|
|
|
+ return "Tested";
|
|
|
|
|
+ case TransactionTask.INSTALLING:
|
|
|
|
|
+ return "Installed";
|
|
|
|
|
+ case TransactionTask.REMOVING:
|
|
|
|
|
+ return "Removed";
|
|
|
|
|
+ case TransactionTask.REBUILDING:
|
|
|
|
|
+ return "Rebuilt";
|
|
|
case TransactionTask.CLEANING_UP:
|
|
case TransactionTask.CLEANING_UP:
|
|
|
- return "cleaned up";
|
|
|
|
|
|
|
+ return "Cleaned up";
|
|
|
default:
|
|
default:
|
|
|
return task.get_verb();
|
|
return task.get_verb();
|
|
|
}
|
|
}
|
|
@@ -380,7 +406,8 @@ namespace Usm.Cli {
|
|
|
/**
|
|
/**
|
|
|
* The terminal width of stderr: TIOCGWINSZ first, then
|
|
* The terminal width of stderr: TIOCGWINSZ first, then
|
|
|
* `$COLUMNS`, then 80 — mirroring how shells and common CLI
|
|
* `$COLUMNS`, then 80 — mirroring how shells and common CLI
|
|
|
- * tooling pick a fallback width.
|
|
|
|
|
|
|
+ * tooling pick a fallback width. Queried per draw rather than
|
|
|
|
|
+ * cached, which is what makes the display resize-responsive.
|
|
|
*/
|
|
*/
|
|
|
private int detect_terminal_width() {
|
|
private int detect_terminal_width() {
|
|
|
WinSize window = WinSize();
|
|
WinSize window = WinSize();
|