@@ -5,9 +5,22 @@ use std::{
55} ;
66
77use clap:: { CommandFactory , Parser } ;
8+ use rayon:: prelude:: * ;
89
910use gdscript_formatter:: { FormatterConfig , formatter:: format_gdscript_with_config} ;
1011
12+ /// This struct is used to hold all the information about the result when
13+ /// formatting a single file. Now that we use parallel processing, we need to
14+ /// keep track of the original index to order the files in the output when
15+ /// printing results.
16+ #[ derive( Debug , Clone ) ]
17+ struct FormatterOutput {
18+ index : usize ,
19+ file_path : PathBuf ,
20+ formatted_content : String ,
21+ is_formatted : bool ,
22+ }
23+
1124#[ derive( Parser ) ]
1225#[ clap(
1326 about = "A GDScript code formatter using Topiary and Tree-sitter" ,
@@ -122,37 +135,84 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
122135 }
123136
124137 let total_files = input_gdscript_files. len ( ) ;
125- let mut all_formatted = true ;
126138
127- for ( index, file_path) in input_gdscript_files. iter ( ) . enumerate ( ) {
128- let file_number = index + 1 ;
129- terminal_clear_line ( ) ;
130- eprint ! ( "\r Formatting file {}/{}" , file_number, total_files) ;
131- io:: stdout ( ) . flush ( ) . unwrap ( ) ;
132-
133- let input_content = fs:: read_to_string ( file_path)
134- . map_err ( |error| format ! ( "Failed to read file {}: {}" , file_path. display( ) , error) ) ?;
139+ eprint ! (
140+ "Formatting {} file{}..." ,
141+ total_files,
142+ if total_files == 1 { "" } else { "s" }
143+ ) ;
144+ io:: stdout ( ) . flush ( ) . unwrap ( ) ;
145+
146+ // We use the rayon library to automatically process files in parallel for
147+ // us. The formatter runs largely single threaded so this speeds things up a
148+ // lot on multi-core CPUs
149+ let outputs: Vec < Result < FormatterOutput , String > > = input_gdscript_files
150+ . par_iter ( )
151+ . enumerate ( )
152+ . map ( |( index, file_path) | {
153+ let input_content = fs:: read_to_string ( file_path) . map_err ( |error| {
154+ format ! ( "Failed to read file {}: {}" , file_path. display( ) , error)
155+ } ) ?;
156+
157+ let formatted_content =
158+ format_gdscript_with_config ( & input_content, & config) . map_err ( |error| {
159+ format ! ( "Failed to format file {}: {}" , file_path. display( ) , error)
160+ } ) ?;
161+
162+ let is_formatted = input_content == formatted_content;
163+
164+ Ok ( FormatterOutput {
165+ index,
166+ file_path : ( * file_path) . clone ( ) ,
167+ formatted_content,
168+ is_formatted,
169+ } )
170+ } )
171+ . collect ( ) ;
135172
136- let formatted_content = format_gdscript_with_config ( & input_content, & config) ?;
173+ // Restore the original order of the input files based on their initial index
174+ let mut sorted_outputs: Vec < _ > = outputs. into_iter ( ) . collect ( ) ;
175+ sorted_outputs. sort_by_key ( |output| {
176+ match output {
177+ Ok ( output) => output. index ,
178+ // Sort errors at the end in no particular order
179+ Err ( _) => usize:: MAX ,
180+ }
181+ } ) ;
137182
138- if args. check {
139- if input_content != formatted_content {
140- all_formatted = false ;
183+ // If true, all input files were already formatted (used for check mode)
184+ let mut all_formatted = true ;
185+ for output in sorted_outputs {
186+ match output {
187+ Ok ( output) => {
188+ if args. check {
189+ if !output. is_formatted {
190+ all_formatted = false ;
191+ }
192+ } else if args. stdout {
193+ // Clear the progress message before printing formatted files to stdout
194+ terminal_clear_line ( ) ;
195+ // A little bit hacky, but because terminals by default output both stdout and stderr
196+ // we need to return carriage to the start to print formatted output from the start of the line
197+ eprint ! ( "\r " ) ;
198+ // If there are multiple input files we still allow stdout but we print a separator
199+ if total_files > 1 {
200+ println ! ( "#--file:{}" , output. file_path. display( ) ) ;
201+ }
202+ print ! ( "{}" , output. formatted_content) ;
203+ } else {
204+ fs:: write ( & output. file_path , output. formatted_content ) . map_err ( |e| {
205+ format ! (
206+ "Failed to write to file {}: {}" ,
207+ output. file_path. display( ) ,
208+ e
209+ )
210+ } ) ?;
211+ }
141212 }
142- } else if args. stdout {
143- // Clear the current line before printing formatted files to stdout, to erase the "Formatting file ..." message
144- terminal_clear_line ( ) ;
145- // A little bit hacky, but because terminals by default output both stdout and stderr
146- // we need to return carriage to the start to print formatted output from the start of the line
147- eprint ! ( "\r " ) ;
148- // If there are multiple input files we still allow stdout but we print a separator
149- if total_files > 1 {
150- println ! ( "#--file:{}" , file_path. display( ) ) ;
213+ Err ( error_msg) => {
214+ return Err ( error_msg. into ( ) ) ;
151215 }
152- print ! ( "{}" , formatted_content) ;
153- } else {
154- fs:: write ( file_path, formatted_content)
155- . map_err ( |e| format ! ( "Failed to write to file {}: {}" , file_path. display( ) , e) ) ?;
156216 }
157217 }
158218
0 commit comments