11mod args;
22mod logging;
3+ mod printer;
34mod python_version;
45mod version;
56
67pub use args:: Cli ;
78use ty_static:: EnvVars ;
89
9- use std:: io :: { self , BufWriter , Write , stdout } ;
10+ use std:: fmt :: Write ;
1011use std:: process:: { ExitCode , Termination } ;
1112
1213use anyhow:: Result ;
1314use std:: sync:: Mutex ;
1415
1516use crate :: args:: { CheckCommand , Command , TerminalColor } ;
1617use crate :: logging:: setup_tracing;
18+ use crate :: printer:: Printer ;
1719use anyhow:: { Context , anyhow} ;
1820use clap:: { CommandFactory , Parser } ;
1921use colored:: Colorize ;
@@ -25,7 +27,7 @@ use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
2527use salsa:: plumbing:: ZalsaDatabase ;
2628use ty_project:: metadata:: options:: ProjectOptionsOverrides ;
2729use ty_project:: watch:: ProjectWatcher ;
28- use ty_project:: { Db , DummyReporter , Reporter , watch} ;
30+ use ty_project:: { Db , watch} ;
2931use ty_project:: { ProjectDatabase , ProjectMetadata } ;
3032use ty_server:: run_server;
3133
@@ -42,14 +44,16 @@ pub fn run() -> anyhow::Result<ExitStatus> {
4244 Command :: Check ( check_args) => run_check ( check_args) ,
4345 Command :: Version => version ( ) . map ( |( ) | ExitStatus :: Success ) ,
4446 Command :: GenerateShellCompletion { shell } => {
47+ use std:: io:: stdout;
48+
4549 shell. generate ( & mut Cli :: command ( ) , & mut stdout ( ) ) ;
4650 Ok ( ExitStatus :: Success )
4751 }
4852 }
4953}
5054
5155pub ( crate ) fn version ( ) -> Result < ( ) > {
52- let mut stdout = BufWriter :: new ( io :: stdout ( ) . lock ( ) ) ;
56+ let mut stdout = Printer :: default ( ) . stream_for_requested_summary ( ) . lock ( ) ;
5357 let version_info = crate :: version:: version ( ) ;
5458 writeln ! ( stdout, "ty {}" , & version_info) ?;
5559 Ok ( ( ) )
@@ -61,6 +65,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
6165 let verbosity = args. verbosity . level ( ) ;
6266 let _guard = setup_tracing ( verbosity, args. color . unwrap_or_default ( ) ) ?;
6367
68+ let printer = Printer :: default ( ) . with_verbosity ( verbosity) ;
69+
6470 tracing:: warn!(
6571 "ty is pre-release software and not ready for production use. \
6672 Expect to encounter bugs, missing features, and fatal errors.",
@@ -125,7 +131,8 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
125131 }
126132
127133 let project_options_overrides = ProjectOptionsOverrides :: new ( config_file, options) ;
128- let ( main_loop, main_loop_cancellation_token) = MainLoop :: new ( project_options_overrides) ;
134+ let ( main_loop, main_loop_cancellation_token) =
135+ MainLoop :: new ( project_options_overrides, printer) ;
129136
130137 // Listen to Ctrl+C and abort the watch mode.
131138 let main_loop_cancellation_token = Mutex :: new ( Some ( main_loop_cancellation_token) ) ;
@@ -143,7 +150,7 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
143150 main_loop. run ( & mut db) ?
144151 } ;
145152
146- let mut stdout = stdout ( ) . lock ( ) ;
153+ let mut stdout = printer . stream_for_requested_summary ( ) . lock ( ) ;
147154 match std:: env:: var ( EnvVars :: TY_MEMORY_REPORT ) . as_deref ( ) {
148155 Ok ( "short" ) => write ! ( stdout, "{}" , db. salsa_memory_dump( ) . display_short( ) ) ?,
149156 Ok ( "mypy_primer" ) => write ! ( stdout, "{}" , db. salsa_memory_dump( ) . display_mypy_primer( ) ) ?,
@@ -192,12 +199,16 @@ struct MainLoop {
192199 /// The file system watcher, if running in watch mode.
193200 watcher : Option < ProjectWatcher > ,
194201
202+ /// Interface for displaying information to the user.
203+ printer : Printer ,
204+
195205 project_options_overrides : ProjectOptionsOverrides ,
196206}
197207
198208impl MainLoop {
199209 fn new (
200210 project_options_overrides : ProjectOptionsOverrides ,
211+ printer : Printer ,
201212 ) -> ( Self , MainLoopCancellationToken ) {
202213 let ( sender, receiver) = crossbeam_channel:: bounded ( 10 ) ;
203214
@@ -207,6 +218,7 @@ impl MainLoop {
207218 receiver,
208219 watcher : None ,
209220 project_options_overrides,
221+ printer,
210222 } ,
211223 MainLoopCancellationToken { sender } ,
212224 )
@@ -223,32 +235,24 @@ impl MainLoop {
223235
224236 // Do not show progress bars with `--watch`, indicatif does not seem to
225237 // handle cancelling independent progress bars very well.
226- self . run_with_progress :: < DummyReporter > ( db) ?;
238+ // TODO(zanieb): We can probably use `MultiProgress` to handle this case in the future.
239+ self . printer = self . printer . with_no_progress ( ) ;
240+ self . run ( db) ?;
227241
228242 Ok ( ExitStatus :: Success )
229243 }
230244
231245 fn run ( self , db : & mut ProjectDatabase ) -> Result < ExitStatus > {
232- self . run_with_progress :: < IndicatifReporter > ( db)
233- }
234-
235- fn run_with_progress < R > ( mut self , db : & mut ProjectDatabase ) -> Result < ExitStatus >
236- where
237- R : Reporter + Default + ' static ,
238- {
239246 self . sender . send ( MainLoopMessage :: CheckWorkspace ) . unwrap ( ) ;
240247
241- let result = self . main_loop :: < R > ( db) ;
248+ let result = self . main_loop ( db) ;
242249
243250 tracing:: debug!( "Exiting main loop" ) ;
244251
245252 result
246253 }
247254
248- fn main_loop < R > ( & mut self , db : & mut ProjectDatabase ) -> Result < ExitStatus >
249- where
250- R : Reporter + Default + ' static ,
251- {
255+ fn main_loop ( mut self , db : & mut ProjectDatabase ) -> Result < ExitStatus > {
252256 // Schedule the first check.
253257 tracing:: debug!( "Starting main loop" ) ;
254258
@@ -264,7 +268,7 @@ impl MainLoop {
264268 // to prevent blocking the main loop here.
265269 rayon:: spawn ( move || {
266270 match salsa:: Cancelled :: catch ( || {
267- let mut reporter = R :: default ( ) ;
271+ let mut reporter = IndicatifReporter :: from ( self . printer ) ;
268272 db. check_with_reporter ( & mut reporter)
269273 } ) {
270274 Ok ( result) => {
@@ -299,10 +303,12 @@ impl MainLoop {
299303 return Ok ( ExitStatus :: Success ) ;
300304 }
301305
302- let mut stdout = stdout ( ) . lock ( ) ;
303-
304306 if result. is_empty ( ) {
305- writeln ! ( stdout, "{}" , "All checks passed!" . green( ) . bold( ) ) ?;
307+ writeln ! (
308+ self . printer. stream_for_success_summary( ) ,
309+ "{}" ,
310+ "All checks passed!" . green( ) . bold( )
311+ ) ?;
306312
307313 if self . watcher . is_none ( ) {
308314 return Ok ( ExitStatus :: Success ) ;
@@ -311,14 +317,19 @@ impl MainLoop {
311317 let mut max_severity = Severity :: Info ;
312318 let diagnostics_count = result. len ( ) ;
313319
320+ let mut stdout = self . printer . stream_for_details ( ) . lock ( ) ;
314321 for diagnostic in result {
315- write ! ( stdout, "{}" , diagnostic. display( db, & display_config) ) ?;
322+ // Only render diagnostics if they're going to be displayed, since doing
323+ // so is expensive.
324+ if stdout. is_enabled ( ) {
325+ write ! ( stdout, "{}" , diagnostic. display( db, & display_config) ) ?;
326+ }
316327
317328 max_severity = max_severity. max ( diagnostic. severity ( ) ) ;
318329 }
319330
320331 writeln ! (
321- stdout ,
332+ self . printer . stream_for_failure_summary ( ) ,
322333 "Found {} diagnostic{}" ,
323334 diagnostics_count,
324335 if diagnostics_count > 1 { "s" } else { "" }
@@ -378,27 +389,53 @@ impl MainLoop {
378389}
379390
380391/// A progress reporter for `ty check`.
381- #[ derive( Default ) ]
382- struct IndicatifReporter ( Option < indicatif:: ProgressBar > ) ;
392+ enum IndicatifReporter {
393+ /// A constructed reporter that is not yet ready, contains the target for the progress bar.
394+ Pending ( indicatif:: ProgressDrawTarget ) ,
395+ /// A reporter that is ready, containing a progress bar to report to.
396+ ///
397+ /// Initialization of the bar is deferred to [`ty_project::ProgressReporter::set_files`] so we
398+ /// do not initialize the bar too early as it may take a while to collect the number of files to
399+ /// process and we don't want to display an empty "0/0" bar.
400+ Initialized ( indicatif:: ProgressBar ) ,
401+ }
402+
403+ impl From < Printer > for IndicatifReporter {
404+ fn from ( printer : Printer ) -> Self {
405+ Self :: Pending ( printer. progress_target ( ) )
406+ }
407+ }
383408
384- impl ty_project:: Reporter for IndicatifReporter {
409+ impl ty_project:: ProgressReporter for IndicatifReporter {
385410 fn set_files ( & mut self , files : usize ) {
386- let progress = indicatif:: ProgressBar :: new ( files as u64 ) ;
387- progress. set_style (
411+ let target = match std:: mem:: replace (
412+ self ,
413+ IndicatifReporter :: Pending ( indicatif:: ProgressDrawTarget :: hidden ( ) ) ,
414+ ) {
415+ Self :: Pending ( target) => target,
416+ Self :: Initialized ( _) => panic ! ( "The progress reporter should only be initialized once" ) ,
417+ } ;
418+
419+ let bar = indicatif:: ProgressBar :: with_draw_target ( Some ( files as u64 ) , target) ;
420+ bar. set_style (
388421 indicatif:: ProgressStyle :: with_template (
389422 "{msg:8.dim} {bar:60.green/dim} {pos}/{len} files" ,
390423 )
391424 . unwrap ( )
392425 . progress_chars ( "--" ) ,
393426 ) ;
394- progress. set_message ( "Checking" ) ;
395-
396- self . 0 = Some ( progress) ;
427+ bar. set_message ( "Checking" ) ;
428+ * self = Self :: Initialized ( bar) ;
397429 }
398430
399431 fn report_file ( & self , _file : & ruff_db:: files:: File ) {
400- if let Some ( ref progress_bar) = self . 0 {
401- progress_bar. inc ( 1 ) ;
432+ match self {
433+ IndicatifReporter :: Initialized ( progress_bar) => {
434+ progress_bar. inc ( 1 ) ;
435+ }
436+ IndicatifReporter :: Pending ( _) => {
437+ panic ! ( "`report_file` called before `set_files`" )
438+ }
402439 }
403440 }
404441}
0 commit comments