@@ -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
5962impl 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) ]
0 commit comments