Ver Fonte

feat: two-line progress bar — overall transaction bar (fixed width) + transient action row replaced by completion logs

clanker há 1 semana atrás
pai
commit
0cf04006c5
3 ficheiros alterados com 1374 adições e 1252 exclusões
  1. 1215 1188
      installer/install-usm.sh
  2. 159 63
      src/cli/ProgressBar.vala
  3. 0 1
      src/lib/State/CachedPackage.vala

Diff do ficheiro suprimidas por serem muito extensas
+ 1215 - 1188
installer/install-usm.sh


+ 159 - 63
src/cli/ProgressBar.vala

@@ -1,14 +1,28 @@
 namespace Usm.Cli {
 
     /**
-     * Single-line, in-place progress bar for a running
-     * {@link Usm.Transaction}: the bar redraws on the bottom line while
-     * completed packages scroll above it, coloured per
+     * 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:
+     *
+     * ```
+     * ✓ falpha-1.0.0 installed
+     *   Building fgamma-1.0.0 (67%)
+     * [████████████████░░░░░░░░░░░░░░░░░░░] 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,
      * magenta test, yellow remove/rebuild/clean-up, cyan strategy, red
-     * failure). When stderr is not a terminal the bar degrades to plain
-     * `[42%] Installing invercargill (2/5)` lines on 10% steps with no
-     * ANSI codes.
+     * 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.
      *
      * Connect {@link on_transaction_progress} to
      * {@link Usm.Transaction.progress_updated}:
@@ -37,10 +51,14 @@ namespace Usm.Cli {
         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_empty_char;
         private string success_mark;
         private string failure_mark;
+        private bool unicode;
 
         private string? current_package;
         private TransactionTask current_task;
@@ -48,11 +66,24 @@ namespace Usm.Cli {
         private int current_index;
         private int current_total;
 
-        /** A package's completion line fires exactly once, at CLEANING_UP's final 1.0 report. */
+        /** 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
+         * `(task + task progress) / total tasks`. Task weighting is the
+         * only scale the signal offers (removals weigh 3 tasks,
+         * installs 5), and the maximum keeps the bar monotonic through
+         * build-cache clean retries that restart a task's progress.
+         */
+        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;
+
         private string plain_key = "";
         private int plain_decile = -1;
 
@@ -63,12 +94,13 @@ namespace Usm.Cli {
             Intl.setlocale(LocaleCategory.ALL, "");
             interactive = Posix.isatty(Posix.STDERR_FILENO);
             width = detect_terminal_width();
+            bar_capacity = int.max(10, int.min(40, width - 40));
             unowned string charset;
-            var utf8 = get_charset(out charset);
-            bar_filled_char = utf8 ? "█" : "#";
-            bar_empty_char = utf8 ? "░" : "-";
-            success_mark = utf8 ? "✓" : "+";
-            failure_mark = utf8 ? "✗" : "x";
+            unicode = get_charset(out charset);
+            bar_filled_char = unicode ? "█" : "#";
+            bar_empty_char = unicode ? "░" : "-";
+            success_mark = unicode ? "✓" : "+";
+            failure_mark = unicode ? "✗" : "x";
         }
 
         /**
@@ -80,15 +112,13 @@ namespace Usm.Cli {
         }
 
         /**
-         * Records one progress report and redraws the bar in place. A
-         * package whose CLEANING_UP reaches 100% — its last report — gets
-         * a completion line printed above the bar first (see
+         * 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}).
          */
         public void update(TransactionTask task, float progress, string? package_name, int index, int total) {
-            // A package's CLEANING_UP reaching 100% is its final report;
-            // the completion line prints above the bar — in plain mode
-            // after its own 100% line — so items scroll as they finish
             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
@@ -111,21 +141,20 @@ namespace Usm.Cli {
             current_progress = progress < 0.0f ? 0.0f : progress > 1.0f ? 1.0f : progress;
             current_index = index;
             current_total = total;
+            var reported = current_total > 0 ? ((float)current_index + current_progress) / (float)current_total : 0.0f;
+            overall = reported > overall ? reported : overall;
 
             if(completes_now) {
                 if(!interactive) {
                     print_plain();
                 }
-                print_completion(package_name, outcome, true, false);
+                print_completion(package_name, outcome, true);
                 completion_printed = true;
-                if(interactive) {
-                    redraw();
-                }
                 return;
             }
 
             if(interactive) {
-                redraw();
+                draw_region();
             }
             else {
                 print_plain();
@@ -133,78 +162,131 @@ namespace Usm.Cli {
         }
 
         /**
-         * Prints a completion summary line above the bar —
-         * `✓ invercargill-1.1.0 installed` in the task colour, or
-         * `✗ invercargill-1.1.0 failed` in red — then redraws the bar.
+         * 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.
          */
         public void complete_line(string package_name, TransactionTask task, bool success) {
-            print_completion(package_name, task, success, true);
+            print_completion(package_name, task, success);
         }
 
         /**
          * Marks the in-flight package failed (a thrown
-         * {@link Usm.TransactionError} aborts the run): erases the bar
-         * and prints `✗ <package> failed` in red — no redraw, the
-         * transaction is over. Harmless when nothing was drawn yet.
+         * {@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.
          */
         public void fail() {
-            if(interactive) {
-                stderr.printf("\r\x1b[K");
-            }
-            if(current_package == null || completion_printed) {
+            if(!interactive) {
+                if(current_package != null && !completion_printed) {
+                    completion_printed = true;
+                    stderr.printf("%s %s failed\n", failure_mark, current_package);
+                }
                 return;
             }
-            completion_printed = true;
-            if(interactive) {
-                stderr.printf("%s%s %s failed%s\n", COLOUR_RED, failure_mark, current_package, COLOUR_RESET);
+
+            if(bar_started) {
+                collapse_region();
             }
-            else {
-                stderr.printf("%s %s failed\n", failure_mark, current_package);
+            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);
             }
         }
 
         /**
-         * Erases the bar line, leaving the cursor at column zero of an
-         * empty line — the effect of a closing newline without a stray
-         * blank line when completion lines were printed last.
+         * Erases the managed region and prints the closing summary line,
+         * leaving the cursor on a fresh row for the caller's own totals.
          */
         public void finish() {
-            if(interactive) {
-                stderr.printf("\r\x1b[K");
+            if(!interactive || !bar_started) {
+                return;
             }
+            collapse_region();
+            stderr.printf("%s%s Transaction completed%s\r\n", COLOUR_GREEN, success_mark, COLOUR_RESET);
         }
 
-        private void print_completion(string package_name, TransactionTask task, bool success, bool redraw_bar) {
+        /**
+         * 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.
+         */
+        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;
+            }
+            else {
+                stderr.printf("\r\033[1A\033[K%s\r\033[1B\033[K%s", action, bar);
+            }
+        }
+
+        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;
-            if(interactive) {
-                stderr.printf("\r\x1b[K%s%s %s %s%s\n", success ? colour_for_task(task) : COLOUR_RED, mark, package_name, outcome, COLOUR_RESET);
-                if(redraw_bar) {
-                    redraw();
-                }
+            var colour = success ? colour_for_task(task) : COLOUR_RED;
+            var log = @"$colour$(fit(@"$mark $package_name $outcome"))$COLOUR_RESET";
+
+            if(!interactive) {
+                stderr.printf("%s %s %s\n", mark, package_name, outcome);
+                return;
+            }
+
+            // 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 %s %s\n", mark, package_name, outcome);
+                stderr.printf("%s\r\n", log);
             }
         }
 
-        private void redraw() {
-            var label = label_for(current_task, current_package);
+        /** 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;
+            }
+            bar_started = false;
+        }
+
+        private string colourised_action_line() {
+            var colour = colour_for_task(current_task);
             var percent = (int)(current_progress * 100);
-            var counts = current_total > 0 ? @" ($(current_index + 1)/$current_total)" : "";
-            var tail = @" $percent% $label$counts";
-            var capacity = int.max(10, int.min(width - 40, width - tail.length - 2));
+            return @"$colour  $(fit(@"$(label_for(current_task, current_package)) ($percent%)"))$COLOUR_RESET";
+        }
 
-            var filled = (int)(current_progress * capacity);
-            var bar = new StringBuilder();
+        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();
             for(var i = 0; i < filled; i++) {
-                bar.append(bar_filled_char);
+                cells.append(bar_filled_char);
             }
-            for(var i = filled; i < capacity; i++) {
-                bar.append(bar_empty_char);
+            for(var i = filled; i < bar_capacity; i++) {
+                cells.append(bar_empty_char);
             }
-
-            stderr.printf("\r\x1b[K%s[%s]%s%s", colour_for_task(current_task), bar.str, tail, COLOUR_RESET);
+            return @"$colour[$(cells.str)] $percent%$COLOUR_RESET";
         }
 
         private void print_plain() {
@@ -220,6 +302,20 @@ namespace Usm.Cli {
             stderr.printf("[%d%%] %s%s\n", (int)(current_progress * 100), label_for(current_task, current_package), counts);
         }
 
+        /**
+         * 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.
+         */
+        private string fit(string text) {
+            var limit = int.max(8, width - 1);
+            if(text.char_count() <= limit) {
+                return text;
+            }
+            return text.substring(0, text.index_of_nth_char(limit - 1)) + (unicode ? "…" : "~");
+        }
+
         private string label_for(TransactionTask task, string? package_name) {
             if(task == TransactionTask.STRATEGISING) {
                 return "Preparing transaction";

+ 0 - 1
src/lib/State/CachedPackage.vala

@@ -124,7 +124,6 @@ namespace Usm {
                 return path;
             }
 
-            print(@"Extracting $(package_path) to $(path)\n");
             Util.unarchive(package_path, path);
             return path;
         }

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff