Add progress bar for ty check (#17965)

## Summary

Adds a simple progress bar for the `ty check` CLI command. The style is
taken from uv, and like uv the bar is always shown - for smaller
projects it is fast enough that it isn't noticeable. We could
alternatively hide it completely based on some heuristic for the number
of files, or only show it after some amount of time.

I also disabled it when `--watch` is passed, cancelling inflight checks
was leading to zombie progress bars. I think we can fix this by using
[`MultiProgress`](https://docs.rs/indicatif/latest/indicatif/struct.MultiProgress.html)
and managing all the bars globally, but I left that out for now.

Resolves https://github.com/astral-sh/ty/issues/98.
This commit is contained in:
Ibraheem Ahmed 2025-05-09 13:32:27 -04:00 committed by GitHub
parent 25e13debc0
commit e9da1750a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 103 additions and 35 deletions

View file

@ -25,7 +25,7 @@ use ruff_db::Upcast;
use salsa::plumbing::ZalsaDatabase;
use ty_project::metadata::options::Options;
use ty_project::watch::ProjectWatcher;
use ty_project::{watch, Db};
use ty_project::{watch, Db, DummyReporter, Reporter};
use ty_project::{ProjectDatabase, ProjectMetadata};
use ty_server::run_server;
@ -200,22 +200,28 @@ impl MainLoop {
self.watcher = Some(ProjectWatcher::new(watcher, db));
self.run(db)?;
// Do not show progress bars with `--watch`, indicatif does not seem to
// handle cancelling independent progress bars very well.
self.run_with_progress::<DummyReporter>(db)?;
Ok(ExitStatus::Success)
}
fn run(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
fn run(self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
self.run_with_progress::<IndicatifReporter>(db)
}
fn run_with_progress<R: Reporter>(mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
self.sender.send(MainLoopMessage::CheckWorkspace).unwrap();
let result = self.main_loop(db);
let result = self.main_loop::<R>(db);
tracing::debug!("Exiting main loop");
result
}
fn main_loop(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
fn main_loop<R: Reporter>(&mut self, db: &mut ProjectDatabase) -> Result<ExitStatus> {
// Schedule the first check.
tracing::debug!("Starting main loop");
@ -226,11 +232,12 @@ impl MainLoop {
MainLoopMessage::CheckWorkspace => {
let db = db.clone();
let sender = self.sender.clone();
let reporter = R::default();
// Spawn a new task that checks the project. This needs to be done in a separate thread
// to prevent blocking the main loop here.
rayon::spawn(move || {
match db.check() {
match db.check(&reporter) {
Ok(result) => {
// Send the result back to the main loop for printing.
sender
@ -340,6 +347,34 @@ impl MainLoop {
}
}
/// A progress reporter for `ty check`.
struct IndicatifReporter(indicatif::ProgressBar);
impl Default for IndicatifReporter {
fn default() -> IndicatifReporter {
let progress = indicatif::ProgressBar::new(0);
progress.set_style(
indicatif::ProgressStyle::with_template(
"{msg:8.dim} {bar:60.green/dim} {pos}/{len} files",
)
.unwrap()
.progress_chars("--"),
);
progress.set_message("Checking");
IndicatifReporter(progress)
}
}
impl ty_project::Reporter for IndicatifReporter {
fn set_files(&self, files: usize) {
self.0.set_length(files as u64);
}
fn report_file(&self, _file: &ruff_db::files::File) {
self.0.inc(1);
}
}
#[derive(Debug)]
struct MainLoopCancellationToken {
sender: crossbeam_channel::Sender<MainLoopMessage>,