Skip to content
Open
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
63 changes: 61 additions & 2 deletions crates/prek/src/cli/reporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,53 @@ use crate::hook::Hook;
use crate::printer::Printer;
use crate::workspace;

const SPINNER_TICK: Duration = Duration::from_millis(200);

// Windows VT keep-alive to prevent ANSI corruption during subprocess execution.
//
// Some Windows tools (uv, pip, npm) disable ENABLE_VIRTUAL_TERMINAL_PROCESSING on exit,
// causing indicatif's spinner output to render as raw escape sequences. This background
// thread re-enables VT mode periodically while progress bars are active.
#[cfg(windows)]
mod vt_keepalive {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};

pub(super) struct VtKeepAlive {
stop: Arc<AtomicBool>,
handle: Option<JoinHandle<()>>,
}

impl VtKeepAlive {
pub(super) fn new() -> Self {
let stop = Arc::new(AtomicBool::new(false));
let stop_clone = stop.clone();

let handle = thread::spawn(move || {
while !stop_clone.load(Ordering::Relaxed) {
let _ = anstyle_query::windows::enable_ansi_colors();
thread::sleep(super::SPINNER_TICK);
}
});

Self {
stop,
handle: Some(handle),
}
}
}

impl Drop for VtKeepAlive {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
}
}

/// Current progress reporter used to suspend rendering while printing normal output.
static CURRENT_REPORTER: Mutex<Option<Weak<ProgressReporter>>> = Mutex::new(None);

Expand Down Expand Up @@ -53,15 +100,27 @@ struct ProgressReporter {
root: ProgressBar,
state: Arc<Mutex<BarState>>,
children: MultiProgress,
#[cfg(windows)]
_vt_keepalive: Option<vt_keepalive::VtKeepAlive>,
}

impl ProgressReporter {
fn new(root: ProgressBar, children: MultiProgress, printer: Printer) -> Self {
// Only spawn the VT keep-alive when progress bars are visible and color is enabled.
#[cfg(windows)]
let vt_keepalive = if printer == Printer::Default && *crate::run::USE_COLOR {
Some(vt_keepalive::VtKeepAlive::new())
} else {
None
};

Self {
printer,
root,
state: Arc::default(),
children,
#[cfg(windows)]
_vt_keepalive: vt_keepalive,
}
}

Expand Down Expand Up @@ -101,7 +160,7 @@ impl From<Printer> for ProgressReporter {
fn from(printer: Printer) -> Self {
let multi = MultiProgress::with_draw_target(printer.target());
let root = multi.add(ProgressBar::with_draw_target(None, printer.target()));
root.enable_steady_tick(Duration::from_millis(200));
root.enable_steady_tick(SPINNER_TICK);
root.set_style(
ProgressStyle::with_template("{spinner:.white} {msg:.dim}")
.unwrap()
Expand Down Expand Up @@ -206,7 +265,7 @@ impl HookRunReporter {
);

let dots = self.dots.saturating_sub(hook.name.width());
progress.enable_steady_tick(Duration::from_millis(200));
progress.enable_steady_tick(SPINNER_TICK);
progress.set_style(
ProgressStyle::with_template(&format!("{{msg}}{{bar:{dots}.green/dim}}"))
.unwrap()
Expand Down
15 changes: 15 additions & 0 deletions crates/prek/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ use tracing::trace;

use crate::git::GIT;

#[cfg(windows)]
fn reenable_vt() {
if *crate::run::USE_COLOR {
let _ = anstyle_query::windows::enable_ansi_colors();
}
}

/// An error from executing a Command
#[derive(Debug, Error)]
pub enum Error {
Expand Down Expand Up @@ -176,6 +183,12 @@ impl Cmd {
summary: self.summary.clone(),
cause,
})?;

// Re-enable Windows VT mode in case subprocess corrupted console mode.
// Some tools disable ENABLE_VIRTUAL_TERMINAL_PROCESSING on exit.
#[cfg(windows)]
reenable_vt();

self.maybe_check_output(&output)?;
Ok(output)
}
Expand Down Expand Up @@ -283,6 +296,8 @@ impl Cmd {
summary: self.summary.clone(),
cause,
})?;
#[cfg(windows)]
reenable_vt();
self.maybe_check_status(status)?;
Ok(status)
}
Expand Down
Loading