Skip to content

Commit 3e1c2e5

Browse files
authored
Merge branch 'main' into fix-logging
2 parents c4b8de9 + f2457b4 commit 3e1c2e5

4 files changed

Lines changed: 112 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
This file documents the changes made to the formatter with each release. This project uses [semantic versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## Release 0.9.0 (2025-09-25)
6+
7+
This release focuses on performance improvements and adds support for formatting multiple files at once.
8+
9+
### Added
10+
11+
- Support for formatting multiple files at once
12+
- Multi-threading when formatting multiple files for better performance
13+
14+
### Changed
15+
16+
- Improved performance on long GDScript files by 5 to 10%
17+
- Don't parse code multiple times when using `--safe` flag
18+
- Reuse parser instances and trees to reduce memory allocations
19+
- Updated dependencies to latest versions
20+
- Updated Zed editor configuration instructions
21+
22+
### Fixed
23+
24+
- Fixed commas ending up dangling on separate lines in some cases (after lambdas in function calls, arrays, and dictionaries)
25+
- Don't modify original syntax tree for safety checks
26+
527
## Release 0.8.1 (2025-09-23)
628

729
### Changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gdscript-formatter"
3-
version = "0.8.1"
3+
version = "0.9.0"
44
edition = "2024"
55
description = "A GDScript code formatter using Topiary and Tree-sitter"
66
license = "MIT"
@@ -13,6 +13,7 @@ topiary-core = { git = "https://github.com/tweag/topiary", rev = "5081ccef9245fe
1313
tree-sitter-gdscript = { git = "https://github.com/NathanLovato/tree-sitter-gdscript.git", branch = "gdquest/GDScript-formatter" }
1414
regex = "1.11"
1515
tree-sitter = "0.25.10"
16+
rayon = "1.11.0"
1617

1718
[dev-dependencies]
1819
test_each_file = "0.3.5"

src/main.rs

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,22 @@ use std::{
55
};
66

77
use clap::{CommandFactory, Parser};
8+
use rayon::prelude::*;
89

910
use 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!("\rFormatting 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

Comments
 (0)