From 8a334e129a16dd3c70257e78a9feaf91a12def4c Mon Sep 17 00:00:00 2001 From: jdx <216188+jdx@users.noreply.github.com> Date: Thu, 14 Aug 2025 08:29:56 -0500 Subject: [PATCH] feat: added header bar during `mise install` --- src/cli/version.rs | 7 +++- src/toolset/mod.rs | 17 ++++++++ src/ui/multi_progress_report.rs | 67 ++++++++++++++++++++++------- src/ui/progress_report.rs | 74 +++++++++++++++++++++++++++++---- 4 files changed, 140 insertions(+), 25 deletions(-) diff --git a/src/cli/version.rs b/src/cli/version.rs index e6e8b2fafa..69c3a07e63 100644 --- a/src/cli/version.rs +++ b/src/cli/version.rs @@ -60,12 +60,17 @@ pub static ARCH: Lazy = Lazy::new(|| { .to_string() }); -pub static VERSION: Lazy = Lazy::new(|| { +pub static VERSION_PLAIN: Lazy = Lazy::new(|| { let mut v = V.to_string(); if cfg!(debug_assertions) { v.push_str("-DEBUG"); }; + v +}); + +pub static VERSION: Lazy = Lazy::new(|| { let build_time = BUILD_TIME.format("%Y-%m-%d"); + let v = &*VERSION_PLAIN; format!("{v} {os}-{arch} ({build_time})", os = *OS, arch = *ARCH) }); diff --git a/src/toolset/mod.rs b/src/toolset/mod.rs index f9f5f4db07..3bb71024d8 100644 --- a/src/toolset/mod.rs +++ b/src/toolset/mod.rs @@ -209,6 +209,10 @@ impl Toolset { return Ok(vec![]); } + // Initialize a header for the entire install session once (before batching) + let mpr = MultiProgressReport::get(); + mpr.init_header("install", versions.len()); + // Run pre-install hook hooks::run_one_hook(config, self, Hooks::Preinstall, None).await; @@ -230,6 +234,8 @@ impl Toolset { successful_installations, failed_installations, }) => { + // Count both successes and failures toward header progress + mpr.header_inc(successful_installations.len() + failed_installations.len()); installed.extend(successful_installations); return Err(Error::InstallFailed { successful_installations: installed, @@ -279,6 +285,8 @@ impl Toolset { // Run post-install hook (ignoring errors) let _ = hooks::run_one_hook(config, self, Hooks::Postinstall, None).await; + // Finish the global header + mpr.header_finish(); Ok(installed) } @@ -315,6 +323,10 @@ impl Toolset { } }; + // Initialize global header progress (overall tools) + let mpr = MultiProgressReport::get(); + mpr.init_header("install", versions_clone.len()); + // Track plugin installation errors to avoid early returns let mut plugin_errors = Vec::new(); @@ -427,6 +439,8 @@ impl Toolset { .await; results.push((tr, result)); + // Bump header for each completed tool + MultiProgressReport::get().header_inc(1); } results }); @@ -467,6 +481,9 @@ impl Toolset { } } + // Finish header bar + MultiProgressReport::get().header_finish(); + // Return appropriate result if failed_installations.is_empty() { Ok(successful_installations) diff --git a/src/ui/multi_progress_report.rs b/src/ui/multi_progress_report.rs index ea1ca9a4ab..aaf141a984 100644 --- a/src/ui/multi_progress_report.rs +++ b/src/ui/multi_progress_report.rs @@ -1,35 +1,33 @@ -use std::sync::{Arc, Mutex, Weak}; +use std::sync::{Arc, Mutex}; use indicatif::MultiProgress; -use crate::config::Settings; -use crate::ui::progress_report::{ProgressReport, QuietReport, SingleReport, VerboseReport}; +use crate::ui::progress_report::{ + HeaderReport, ProgressReport, QuietReport, SingleReport, VerboseReport, +}; +use crate::ui::style; +use crate::{cli::version::VERSION_PLAIN, config::Settings}; #[derive(Debug)] pub struct MultiProgressReport { mp: Option, quiet: bool, + header: Mutex>, } -static INSTANCE: Mutex>> = Mutex::new(None); +static INSTANCE: Mutex>> = Mutex::new(None); impl MultiProgressReport { pub fn try_get() -> Option> { - match &*INSTANCE.lock().unwrap() { - Some(w) => w.upgrade(), - None => None, - } + INSTANCE.lock().unwrap().as_ref().cloned() } pub fn get() -> Arc { - let mut mutex = INSTANCE.lock().unwrap(); - if let Some(w) = &*mutex { - if let Some(mpr) = w.upgrade() { - return mpr; - } + let mut guard = INSTANCE.lock().unwrap(); + if let Some(existing) = guard.as_ref() { + return existing.clone(); } - let mpr = Arc::new(Self::new()); - *mutex = Some(Arc::downgrade(&mpr)); + *guard = Some(mpr.clone()); mpr } fn new() -> Self { @@ -45,6 +43,7 @@ impl MultiProgressReport { MultiProgressReport { mp, quiet: settings.quiet, + header: Mutex::new(None), } } pub fn add(&self, prefix: &str) -> Box { @@ -58,6 +57,44 @@ impl MultiProgressReport { None => Box::new(VerboseReport::new(prefix.to_string())), } } + pub fn init_header(&self, label: &str, total_tools: usize) { + if self.mp.is_none() || self.quiet { + return; + } + let mut hdr = self.header.lock().unwrap(); + match (&self.mp, hdr.as_ref()) { + (Some(mp), None) => { + let version = &*VERSION_PLAIN; + let prefix = format!( + "{} {} {}", + style::emagenta("mise").bold(), + style::edim(format!("{version} by @jdx –")), + style::eblue(label), + ); + let mut header = HeaderReport::new(prefix); + header.pb = mp.add(header.pb); + header.set_length(total_tools as u64); + *hdr = Some(header); + } + (_, Some(_h)) => { + // header already initialized; do not change total + } + _ => {} + } + } + pub fn header_inc(&self, n: usize) { + if n == 0 { + return; + } + if let Some(h) = &*self.header.lock().unwrap() { + h.inc(n as u64); + } + } + pub fn header_finish(&self) { + if let Some(h) = &*self.header.lock().unwrap() { + h.finish_with_message("installed".to_string()); + } + } pub fn suspend_if_active R, R>(f: F) -> R { match Self::try_get() { Some(mpr) => mpr.suspend(f), diff --git a/src/ui/progress_report.rs b/src/ui/progress_report.rs index 6558276109..8ed238452e 100644 --- a/src/ui/progress_report.rs +++ b/src/ui/progress_report.rs @@ -43,6 +43,16 @@ static SUCCESS_TEMPLATE: Lazy = Lazy::new(|| { ProgressStyle::with_template(tmpl.as_str()).unwrap() }); +static HEADER_TEMPLATE: Lazy = Lazy::new(|| { + let width = match *env::TERM_WIDTH { + 0..=79 => 10, + 80..=99 => 15, + _ => 20, + }; + let tmpl = format!(r#"{{prefix}} {{bar:{width}.cyan/blue}} {{pos}}/{{len:2}}"#); + ProgressStyle::with_template(&tmpl).unwrap() +}); + #[derive(Debug)] pub struct ProgressReport { pub pb: ProgressBar, @@ -118,16 +128,10 @@ impl SingleReport for ProgressReport { self.pb.abandon(); } fn finish(&self) { - self.pb.set_style(SUCCESS_TEMPLATE.clone()); - self.pb - .set_prefix(success_prefix(self.pad - 2, &self.prefix)); - self.pb.finish() + self.pb.finish_and_clear(); } - fn finish_with_message(&self, message: String) { - self.pb.set_style(SUCCESS_TEMPLATE.clone()); - self.pb - .set_prefix(success_prefix(self.pad - 2, &self.prefix)); - self.pb.finish_with_message(message); + fn finish_with_message(&self, _message: String) { + self.pb.finish_and_clear(); } } @@ -180,6 +184,58 @@ impl SingleReport for VerboseReport { } } +#[derive(Debug)] +pub struct HeaderReport { + pub pb: ProgressBar, +} + +impl HeaderReport { + pub fn new(prefix: String) -> HeaderReport { + ui::ctrlc::show_cursor_after_ctrl_c(); + let pb = ProgressBar::new(100) + .with_style(HEADER_TEMPLATE.clone()) + .with_prefix(prefix); + HeaderReport { pb } + } +} + +impl SingleReport for HeaderReport { + fn println(&self, message: String) { + self.pb.suspend(|| { + eprintln!("{message}"); + }); + } + fn set_message(&self, message: String) { + self.pb.set_message(message.replace('\r', "")); + } + fn inc(&self, delta: u64) { + self.pb.inc(delta); + } + fn set_position(&self, pos: u64) { + self.pb.set_position(pos); + if Some(self.pb.position()) == self.pb.length() { + self.pb.set_style(SPIN_TEMPLATE.clone()); + self.pb.enable_steady_tick(Duration::from_millis(250)); + } + } + fn set_length(&self, length: u64) { + self.pb.set_position(0); + self.pb.set_style(HEADER_TEMPLATE.clone()); + self.pb.set_length(length); + } + fn abandon(&self) { + self.pb.abandon(); + } + fn finish(&self) { + self.pb.set_style(SUCCESS_TEMPLATE.clone()); + self.pb.finish() + } + fn finish_with_message(&self, message: String) { + self.pb.set_style(SUCCESS_TEMPLATE.clone()); + self.pb.finish_with_message(message); + } +} + #[cfg(test)] mod tests { use crate::config::Config;