Browse Source

feat: indeterminate pulsing bar for SPM queries and pre-Content-Length fetches; fix right-alignment (ANSI-aware padding); fix off-by-one task counter (strategise resets)

clanker 1 week ago
parent
commit
60426f76cd
4 changed files with 137 additions and 249 deletions
  1. 1 235
      installer/install-usm.sh
  2. 13 4
      src/cli/Install.vala
  3. 118 10
      src/cli/ProgressBar.vala
  4. 5 0
      src/lib/Transaction.vala

File diff suppressed because it is too large
+ 1 - 235
installer/install-usm.sh


+ 13 - 4
src/cli/Install.vala

@@ -38,9 +38,14 @@ private int install_main(string[] args) {
         var resolver = new Usm.Resolver(local_finder);
         var repos = state.get_repositories();
         foreach (var repo in repos) {
-            progress.begin_phase(@"Refreshing $(repo.name)");
+            progress.begin_indeterminate(@"Refreshing $(repo.name)");
             state.refresh_list(repo, (f, c, t) => {
-                progress.update_phase(t > 0 ? (float)c / (float)t : 0.0f);
+                if(t > 0) {
+                    if(progress.phase_is_indeterminate) {
+                        progress.begin_phase(@"Refreshing $(repo.name)");
+                    }
+                    progress.update_phase((float)c / (float)t);
+                }
             });
             progress.phase_log(@"Refreshed $(repo.name) listing");
             progress.end_phase();
@@ -67,9 +72,13 @@ private int install_main(string[] args) {
         // Resolve the full closure first: local → system packages → USM
         // repositories/cache, with candidate-group selection
         var spm = new Usm.SystemPackageManager(state.config);
-        progress.begin_phase("Resolving dependencies");
-        progress.update_phase(0.05f);
+        progress.begin_indeterminate("Querying system packages");
+        bool spm_done = false;
         resolver.resolution_progress = (fraction) => {
+            if(!spm_done) {
+                spm_done = true;
+                progress.begin_phase("Resolving dependencies");
+            }
             progress.update_phase(fraction);
         };
         Usm.ResolutionResult resolution;

+ 118 - 10
src/cli/ProgressBar.vala

@@ -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;
         }
 

+ 5 - 0
src/lib/Transaction.vala

@@ -235,6 +235,11 @@ namespace Usm {
         }
 
         public void strategise() throws TransactionError {
+            // Reset the task counters — strategise may be called twice
+            // (once for the summary/confirm, once inside run())
+            current_task = 0;
+            task_count = 0;
+
             planned_install = new HashSet<CachedPackage>();
             planned_install.union_with(to_install);
             planned_removal = new HashSet<CachedPackage>();

Some files were not shown because too many files changed in this diff