Skip to content

Commit 44dd067

Browse files
Add support for '# gd-formatter:disable' and '# gd-formatter:enable'. (#240)
* Add support for '# gd-formatter:disable' and '# gd-formatter:enable'. Implements #238. * Switch region markers from gd-formatter:disable/enable to fmt: off/on, refactor the code a little bit * Reintroduce spaces in format disabled test --------- Co-authored-by: Nathan Lovato <12694995+NathanLovato@users.noreply.github.com> Co-authored-by: Nathan Lovato <nathan@gdquest.com>
1 parent 039d405 commit 44dd067

3 files changed

Lines changed: 156 additions & 0 deletions

File tree

src/formatter.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ struct Formatter {
5454
tree: Tree,
5555
original_source: Option<String>,
5656
indent_string: String,
57+
// Original text of each `# fmt: off` ... `# fmt: on` region,
58+
// indexed by the order they appear in the file. Used to restore regions after formatting.
59+
disabled_regions: Vec<String>,
5760
}
5861

5962
impl Formatter {
@@ -87,6 +90,7 @@ impl Formatter {
8790
input_tree,
8891
parser,
8992
indent_string,
93+
disabled_regions: Vec::new(),
9094
}
9195
}
9296

@@ -153,6 +157,119 @@ impl Formatter {
153157
/// pre-applying rules that could be performance-intensive through topiary.
154158
#[inline(always)]
155159
fn preprocess(&mut self) -> &mut Self {
160+
self.extract_disabled_regions();
161+
self
162+
}
163+
164+
/// Scans the content for `# fmt: off` / `# fmt: on` regions (ignoring whitespace).
165+
/// Each complete region (including the marker lines) is stored literally in
166+
/// self.disabled_regions and replaced with a single placeholder comment of the
167+
/// form `# fmt:preserved-region:N`. This prevents Topiary and all
168+
/// post-processing steps from touching the content inside those regions.
169+
fn extract_disabled_regions(&mut self) {
170+
enum LineKind {
171+
FmtOn,
172+
FmtOff,
173+
Other,
174+
}
175+
176+
/// Checks whether `line` is a `# fmt: off` or `# fmt: on` marker.
177+
fn classify_line(line: &str) -> LineKind {
178+
if !line.contains('#') {
179+
return LineKind::Other;
180+
}
181+
182+
let line = line.trim();
183+
let Some(after_hash) = line.strip_prefix('#') else {
184+
return LineKind::Other;
185+
};
186+
let Some(after_fmt) = after_hash.trim_start().strip_prefix("fmt:") else {
187+
return LineKind::Other;
188+
};
189+
190+
match after_fmt.trim_start() {
191+
"off" => LineKind::FmtOff,
192+
"on" => LineKind::FmtOn,
193+
_ => LineKind::Other,
194+
}
195+
}
196+
197+
let mut result = String::new();
198+
let mut in_disabled_region = false;
199+
let mut current_region = String::new();
200+
201+
// split_inclusive keeps the '\n' attached to each line so we never lose
202+
// trailing newlines when we reassemble the string.
203+
for line in self.content.split_inclusive('\n') {
204+
match classify_line(line) {
205+
LineKind::FmtOff if !in_disabled_region => {
206+
in_disabled_region = true;
207+
current_region.push_str(line);
208+
}
209+
LineKind::FmtOn if in_disabled_region => {
210+
current_region.push_str(line);
211+
in_disabled_region = false;
212+
let region_index = self.disabled_regions.len();
213+
self.disabled_regions.push(current_region.clone());
214+
current_region.clear();
215+
result.push_str(&format!("# fmt:preserved-region:{}\n", region_index));
216+
}
217+
_ => {
218+
if in_disabled_region {
219+
current_region.push_str(line);
220+
} else {
221+
result.push_str(line);
222+
}
223+
}
224+
}
225+
}
226+
227+
// An unclosed disable region (no matching enable marker) is also preserved.
228+
if in_disabled_region {
229+
let region_index = self.disabled_regions.len();
230+
self.disabled_regions.push(current_region);
231+
result.push_str(&format!("# fmt:preserved-region:{}\n", region_index));
232+
}
233+
234+
self.content = result;
235+
236+
// Reparse the tree in case we modified the source code and replaced
237+
// some regions with disabled formatting.
238+
if !self.disabled_regions.is_empty() {
239+
self.tree = self.parser.parse(&self.content, None).unwrap();
240+
}
241+
}
242+
243+
/// Replaces every placeholder comment emitted by extract_disabled_regions() with
244+
/// the original region text that was saved at that time. Called as the last
245+
/// post-processing step so all normal formatting has already been applied to the
246+
/// surrounding code.
247+
fn restore_disabled_regions(&mut self) -> &mut Self {
248+
if self.disabled_regions.is_empty() {
249+
return self;
250+
}
251+
252+
let mut result = String::new();
253+
254+
for line in self.content.split_inclusive('\n') {
255+
// Strip leading whitespace before checking for the placeholder; Topiary
256+
// may have adjusted indentation on comment lines.
257+
let trimmed = line.trim();
258+
if let Some(rest) = trimmed.strip_prefix("# fmt:preserved-region:") {
259+
if let Ok(index) = rest.parse::<usize>() {
260+
if let Some(original) = self.disabled_regions.get(index) {
261+
result.push_str(original);
262+
continue;
263+
}
264+
}
265+
}
266+
result.push_str(line);
267+
}
268+
269+
self.content = result;
270+
// Re-parse so self.tree stays in sync with the restored content for any
271+
// subsequent steps (validate_formatting, reorder) that rely on it.
272+
self.tree = self.parser.parse(&self.content, Some(&self.tree)).unwrap();
156273
self
157274
}
158275

@@ -175,6 +292,10 @@ impl Formatter {
175292
.fix_trailing_spaces()
176293
.remove_trailing_commas_from_preload()
177294
.postprocess_tree_sitter()
295+
// Restore the original text of disabled regions after all other post-processing,
296+
// so that the surrounding code is formatted normally while the disabled regions
297+
// keep their exact original content.
298+
.restore_disabled_regions()
178299
}
179300

180301
#[inline(always)]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class_name TestClass
2+
extends Node
3+
4+
# fmt: off
5+
var vertices_preserve: PackedVector3Array = [
6+
Vector3(-1, 0, -1),
7+
Vector3( 1, 0, -1),
8+
Vector3( 1, 0, 1),
9+
Vector3(-1, 0, 1),
10+
]
11+
# fmt: on
12+
var vertices_reformat: PackedVector3Array = [
13+
Vector3(-1, 0, -1),
14+
Vector3(1, 0, -1),
15+
Vector3(1, 0, 1),
16+
Vector3(-1, 0, 1),
17+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class_name TestClass
2+
extends Node
3+
4+
# fmt: off
5+
var vertices_preserve: PackedVector3Array = [
6+
Vector3(-1, 0, -1),
7+
Vector3( 1, 0, -1),
8+
Vector3( 1, 0, 1),
9+
Vector3(-1, 0, 1),
10+
]
11+
# fmt: on
12+
13+
var vertices_reformat: PackedVector3Array = [
14+
Vector3(-1, 0, -1),
15+
Vector3( 1, 0, -1),
16+
Vector3( 1, 0, 1),
17+
Vector3(-1, 0, 1),
18+
]

0 commit comments

Comments
 (0)