Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/pixi_progress/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod osc;
mod placement;
pub mod style;

Expand Down
46 changes: 46 additions & 0 deletions crates/pixi_progress/src/osc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::io::{IsTerminal, Write};
use std::sync::LazyLock;

use parking_lot::Mutex;

/// Cached check for whether stdout is a terminal.
static IS_TERMINAL: LazyLock<bool> = LazyLock::new(|| std::io::stdout().is_terminal());

/// Last emitted percentage, used to avoid redundant writes.
static LAST_PCT: Mutex<Option<u8>> = Mutex::new(None);

/// Emit an OSC 9;4 progress sequence to stdout.
///
/// Terminal emulators that support OSC 9;4 (Windows Terminal, iTerm2,
/// WezTerm, ConEmu, etc.) display this as progress in the title bar
/// or taskbar icon.
///
/// Uses ST (ESC \) as the string terminator for broad compatibility.
pub fn set_progress(position: u64, length: u64) {
if !*IS_TERMINAL || length == 0 || crate::global_multi_progress().is_hidden() {
return;
}
let pct = (position * 100 / length).min(100) as u8;
let mut last = LAST_PCT.lock();
if *last != Some(pct) {
*last = Some(pct);
let seq = format!("\x1b]9;4;1;{pct}\x1b\\");
let mut stdout = std::io::stdout().lock();
let _ = stdout.write_all(seq.as_bytes());
let _ = stdout.flush();
}
}

/// Clear the OSC 9;4 progress indicator.
pub fn clear_progress() {
if !*IS_TERMINAL || crate::global_multi_progress().is_hidden() {
return;
}
let mut last = LAST_PCT.lock();
if last.is_some() {
*last = None;
let mut stdout = std::io::stdout().lock();
let _ = stdout.write_all(b"\x1b]9;4;0;0\x1b\\");
let _ = stdout.flush();
}
}
17 changes: 17 additions & 0 deletions crates/pixi_reporters/src/main_progress_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ struct State<T> {
/// The items that are being tracked by this progress bar.
tracker: Arc<RwLock<HashMap<usize, TrackedItem<T>>>>,
next_tracker_id: usize,

/// Whether to emit OSC 9;4 terminal progress reporting.
osc_report: bool,
}

/// A trait for something that can be tracked by the [`MainProgressBar`].
Expand Down Expand Up @@ -72,10 +75,17 @@ impl<T: Tracker> MainProgressBar<T> {
title: Some(title),
tracker: Arc::new(RwLock::new(HashMap::new())),
next_tracker_id: 0,
osc_report: false,
})),
}
}

/// Enable OSC 9;4 terminal progress reporting on this bar.
pub fn with_osc_report(self) -> Self {
self.inner.write().osc_report = true;
self
}

/// Called when an item is queued for processing.
pub fn queued(&self, tracker: T) -> usize {
let mut state = self.inner.write();
Expand Down Expand Up @@ -120,6 +130,9 @@ impl<T: Tracker> State<T> {

// Clear or update the progress bar.
if is_empty {
if self.osc_report {
pixi_progress::osc::clear_progress();
}
// We cannot clear the progress bar and restart it later, so replacing it with a
// new hidden one is currently the only option.
self.title = Some(self.pb.prefix());
Expand Down Expand Up @@ -225,6 +238,10 @@ impl<T: Tracker> State<T> {
state.set_pos(position);
});
self.pb.set_message(wide_msg);

if self.osc_report {
pixi_progress::osc::set_progress(position, length);
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion crates/pixi_reporters/src/sync_reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ impl CombinedInstallReporterInner {
multi_progress.clone(),
ProgressBarPlacement::After(preparing_progress_bar.progress_bar()),
"installing".to_owned(),
);
)
.with_osc_report();

Self {
next_id: std::sync::atomic::AtomicUsize::new(0),
Expand Down