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

@ -2,7 +2,7 @@ use std::panic::RefUnwindSafe;
use std::sync::Arc;
use crate::DEFAULT_LINT_REGISTRY;
use crate::{Project, ProjectMetadata};
use crate::{Project, ProjectMetadata, Reporter};
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::{File, Files};
use ruff_db::system::System;
@ -68,8 +68,8 @@ impl ProjectDatabase {
}
/// Checks all open files in the project and its dependencies.
pub fn check(&self) -> Result<Vec<Diagnostic>, Cancelled> {
self.with_db(|db| db.project().check(db))
pub fn check(&self, reporter: &impl Reporter) -> Result<Vec<Diagnostic>, Cancelled> {
self.with_db(|db| db.project().check(db, reporter))
}
#[tracing::instrument(level = "debug", skip(self))]

View file

@ -161,6 +161,10 @@ impl Indexed<'_> {
pub(super) fn diagnostics(&self) -> &[IOErrorDiagnostic] {
&self.inner.diagnostics
}
pub(super) fn len(&self) -> usize {
self.inner.files.len()
}
}
impl Deref for Indexed<'_> {

View file

@ -18,7 +18,7 @@ use rustc_hash::FxHashSet;
use salsa::Durability;
use salsa::Setter;
use std::backtrace::BacktraceStatus;
use std::panic::{AssertUnwindSafe, UnwindSafe};
use std::panic::{AssertUnwindSafe, RefUnwindSafe, UnwindSafe};
use std::sync::Arc;
use thiserror::Error;
use tracing::error;
@ -106,6 +106,24 @@ pub struct Project {
settings_diagnostics: Vec<OptionDiagnostic>,
}
/// A progress reporter.
pub trait Reporter: Default + Send + Sync + RefUnwindSafe + 'static {
/// Initialize the reporter with the number of files.
fn set_files(&self, files: usize);
/// Report the completion of a given file.
fn report_file(&self, file: &File);
}
/// A no-op implementation of [`Reporter`].
#[derive(Default)]
pub struct DummyReporter;
impl Reporter for DummyReporter {
fn set_files(&self, _files: usize) {}
fn report_file(&self, _file: &File) {}
}
#[salsa::tracked]
impl Project {
pub fn from_metadata(db: &dyn Db, metadata: ProjectMetadata) -> Self {
@ -168,7 +186,7 @@ impl Project {
}
/// Checks all open files in the project and its dependencies.
pub(crate) fn check(self, db: &ProjectDatabase) -> Vec<Diagnostic> {
pub(crate) fn check(self, db: &ProjectDatabase, reporter: &impl Reporter) -> Vec<Diagnostic> {
let project_span = tracing::debug_span!("Project::check");
let _span = project_span.enter();
@ -182,6 +200,7 @@ impl Project {
);
let files = ProjectFiles::new(db, self);
reporter.set_files(files.len());
diagnostics.extend(
files
@ -190,36 +209,31 @@ impl Project {
.map(IOErrorDiagnostic::to_diagnostic),
);
let file_diagnostics = Arc::new(std::sync::Mutex::new(vec![]));
let file_diagnostics = std::sync::Mutex::new(vec![]);
{
let file_diagnostics = Arc::clone(&file_diagnostics);
let db = db.clone();
let project_span = project_span.clone();
let file_diagnostics = &file_diagnostics;
let project_span = &project_span;
rayon::scope(move |scope| {
for file in &files {
let result = Arc::clone(&file_diagnostics);
let db = db.clone();
let project_span = project_span.clone();
scope.spawn(move |_| {
let check_file_span =
tracing::debug_span!(parent: &project_span, "check_file", ?file);
tracing::debug_span!(parent: project_span, "check_file", ?file);
let _entered = check_file_span.entered();
let file_diagnostics = check_file_impl(&db, file);
result.lock().unwrap().extend(file_diagnostics);
let result = check_file_impl(&db, file);
file_diagnostics.lock().unwrap().extend(result);
reporter.report_file(&file);
});
}
});
}
let mut file_diagnostics = Arc::into_inner(file_diagnostics)
.unwrap()
.into_inner()
.unwrap();
let mut file_diagnostics = file_diagnostics.into_inner().unwrap();
file_diagnostics.sort_by(|left, right| {
left.rendering_sort_key(db)
.cmp(&right.rendering_sort_key(db))
@ -493,6 +507,13 @@ impl<'a> ProjectFiles<'a> {
ProjectFiles::Indexed(indexed) => indexed.diagnostics(),
}
}
fn len(&self) -> usize {
match self {
ProjectFiles::OpenFiles(open_files) => open_files.len(),
ProjectFiles::Indexed(indexed) => indexed.len(),
}
}
}
impl<'a> IntoIterator for &'a ProjectFiles<'a> {