Release 0.4.0.10 (from beta build 3.0.1 / GetIt). All changes below were driven by community feedback from the Delphi-PRAXiS beta threads — thank you to everyone who tested and reported.
1. Fixed
Sources the formatter refused, or changed in ways it must not
Conditional compilation ({$IFDEF} / {$ELSE} / {$ENDIF}) — the big one
Sources whose conditional branches open or close blocks (begin, try, class, asm …) could not be formatted at all: the formatter saw the code of every branch at once, counted openers and closers across branches and refused the whole file as "unbalanced block structure". The mirror case — a closer duplicated across branches — silently produced drifted indentation.
The structural bookkeeping is now branch-aware: at {$IF*} the formatter snapshots its state, walks every {$ELSE} / {$ELSEIF} branch from that same entry state and continues after {$ENDIF} / {$IFEND} with the first branch's exit state. Both directive families are supported, nested regions work, and column alignment no longer bridges across branch boundaries. The safety guarantee is unchanged: nothing is ever written when the source truly is unbalanced.
Known limits: include files ({$I}) that contribute block edges and directives sharing a line with code are by-design limits — now documented in the manual's "Known limits" section; some reflow-mode edge cases remain tracked.
Variant records refused every unit
Any unit containing a variant record (case Integer of inside a record) was refused with the same "unbalanced" message — without a single compiler directive involved. Fixed.
A closing end at the end of a statement line refused the whole file
A line that both finishes a statement and closes its block — B; end;, or the end); a compact one-line anonymous method leaves behind — made the formatter report the source as unbalanced and refuse it entirely, although the code is perfectly valid. The closer was consuming the pending continuation of an enclosing if … then / while … do instead of the block itself, so the block stayed open until the end of the file. A closer now always ends a block, wherever it sits on the line. This affected the default profile too, not just reflow — and in the cases that were not refused outright it silently pushed everything after that line further and further right.
asm blocks that end mid-line refused the file
The same class of defect as the previous fix, one code path over: after the end of an asm block the formatter stopped looking at the rest of that line, so a routine written as asm … end; end; lost its outer block and the file was refused. The remaining closers on the line are now counted.
Reflow could silently change what a string literal means
With line-break rules active, if C = '''' then Q; was reflowed so that the apostrophe literal ended up as the last token on its line. Delphi 12 reads a run of three or more apostrophes followed only by whitespace as the opening delimiter of a multiline string — so on the next run the formatter (correctly) read its own output as a multiline string and swallowed the following code. The result was a program with different meaning, and in some shapes one that no longer even parses. Such a literal is now never left at the end of a line. This also removes the last known case where formatting twice gave a different result than formatting once.
An unrelated .editorconfig could be applied to your file
The settings cascade walks from the file's own directory upwards — but one step past the drive root it produced the drive-relative form (C:), and asking Windows for C:.editorconfig means ".editorconfig in the current directory of that drive". Files that had no .editorconfig of their own therefore silently picked up the one belonging to whatever directory the formatter happened to be started from — in the IDE that is not even a directory you chose. The cascade now stops at the drive root, so only the file's own path decides its settings.
Broken parentheses no longer produce file-wide indent drift
A source with a missing ) or ] used to pass the safety check silently — it only measured begin/end blocks — and the continuation indent then drifted through the rest of the file. Unbalanced parentheses or brackets now share the same protection as unbalanced blocks: the IDE refuses with a precise message ("unbalanced parentheses or brackets") and leaves the buffer untouched. Such files cannot compile anyway; once the syntax is fixed, a single format run restores canonical indentation everywhere.
Layout
Alignment stopped working after branch-asymmetric conditionals
The aligner's nesting counters read every {$IFDEF} branch as if all of them were compiled at once — a begin in each branch counted twice, so behind such a conditional the aligner believed it was still inside executable code and quietly stopped aligning const/var/field blocks. The counters now follow the first branch only (the same first-branch-wins rule the conditional-fork indenter uses); behind the region the alignment is identical to a file containing just that branch.
Generics with conditional type arguments were spaced as comparisons
TList came out as TList ; — the generic matcher silently swallowed the directives and read both branches as one invalid token stream. The scan now follows the first branch and skips {$ELSE}…{$ENDIF} whole (the same first-branch-wins rule the conditional-fork indenter uses), so the angle brackets stay generic-tight in every branch. Real comparisons next to directives keep their operator spacing.
Box-art comment headers were gutted
Under the default profile, decorative comment blocks such as the classic { Framework Name } unit headers lost their interior padding, and whitespace-only filler lines collapsed to {}. The comment-spacing option now does exactly what it says — it normalizes the single space inside the delimiters ({x} ↔ { x }) — and treats everything beyond that (padding runs, frame lines like {******}, multi-line comments) as deliberate layout that survives byte-for-byte.
Nested if/else indented misleadingly
In if A then if B then … else … chains the else branch was visually re-bound to the outer if, although Delphi binds it to the nearest one. The indentation now follows the language's actual binding — including through while … do in between, and without disturbing case-else handling.
Multi-line signatures and calls lost their continuation indent
Continuation lines of a multi-line parameter list or call were flattened to the same column as their head line. They now indent one continuation step past the line that opened the bracket; a closing-bracket line aligns with that opener line.
Statements after a mid-line finally / except were mis-indented
try / if A then / B; finally left the pending then continuation standing, so the statements of the finally part sat one step too deep.
The else of an unparenthesized if-expression looked like a second else
In if C then / X := if A / then 1 / else 2 / else / Y := 3; the first else belongs to the expression, the second to the statement — but the formatter pulled both onto the same column, so the code read as if one if had two else branches. The expression's else now stays at the expression's level. (The branch lines themselves keep their layout: an unparenthesized continuation is never indented an extra step — the same as X := A + / B;.)
Generic constraints were spaced as comparisons
TList came out as TList . Constraint lists (class, record, constructor, interface types, multiple parameter groups) are now recognized as generics.
Generics in parameter defaults were torn apart
A default value glued to a generic parameter type — Symbols: TArray= [] (no space before the =) — made the scanner read >= as a single greater-or-equal operator. The generic then failed to be recognized and came out spaced as a comparison: TArray = []. Both the tight original and the previously damaged form now heal to TArray = []; a generic < also glues back to its type name. Real comparisons like a = c are untouched.
Delphi 13 if-expressions were staircased
A multi-line if-expression in parentheses — (if cond then '-' else '') spread over lines — had its branch lines indented like a statement-if, one extra level per then/else. Combined with the bracket-indent option this produced a staircase drifting right with every branch. Inside parentheses an if is always an expression (only an anonymous method's begin re-enters statement territory — that case is recognized and unchanged), so the branch lines now get plain continuation indent. Statement-if formatting, dangling- else binding and case-else handling are untouched.
"else if" line breaks left the else hanging
With line-break rules active, if A then B else if C then D reflowed to then B else ⏎ if C. A statement-level else now always starts its own line; the existing option keeps controlling the gap between else and if (both settings of it now produce clean shapes). Delphi 13 if-expressions (x := if a then b else c) are never broken apart.
Line-break rules tore if-expressions apart
With reflow enabled, a Delphi 13 if-expression was laid out like a statement: S := (if A then 'yes' else 'no'); came back split across four lines. The first round of protection covered only the unparenthesized form and only two of the four rules involved, so both the parenthesized form and — with the then and simple instruction rules armed — even the bare one were still broken up. Inside parentheses an if is always an expression (an anonymous method's begin re-enters statement territory), and those rules now leave both forms alone. Statement ifs keep breaking exactly as configured.
Command line
One bad file no longer aborts the whole run
A source that cannot be decoded (a stray byte in a file claiming UTF-8) made Formatter.exe stop right there — silently truncating a recursive sweep, even under --check where nothing is written at all. Such a file is now reported, counted as skipped and left untouched; the run continues and the exit code still reports the error.
2. New
Blank lines around initialization/finalization, and before the final end.
Two new structure separators and a new indent option close the gap the section separators left: delphiblanklinesaroundinitialization guarantees blank lines before and after the initialization/ finalization keywords, delphiblanklinesbeforefinal_end puts them before the closing end., and Indent initialization/finalization contents indents the sections' statements one level like a begin..end block. All three are off by default — existing output stays byte-identical.
Unusable options are now hidden
When a master option turns a group of settings off, those settings used to stay visible — at best with a grayed group title, while the controls below still looked active ("It's disturbing"). Dependent options are now hidden outright: with "Keep user line breaks" on (the default), the line-break rules, the whole Reflow page and the blank-line separators disappear (an explanatory note stays behind); the alignment tuning numbers hide while no alignment case is active; coupled or inert switches (colons-before-types coupling, case options without block indent, bracket indent under reflow) vanish with their masters. Toggling the master brings everything back — live, across pages, without reopening the dialog.
Readable, complete option texts
Longer labels were cut off — most visibly the note under Blank Lines and the "colons before type names" switch under Alignment, and more often in German, where the translations are longer than the English originals. Every page was measured against both languages: notes have room for their full text, long switch captions wrap instead of being clipped, and the drop-downs sit far enough right that no label can run into them. The explanatory notes are now laid out in paragraphs instead of one dense block.
The option previews now show real cases
The live before/after preview on each options page demonstrates the constellations this release fixed: conditional branches carrying a try, case with else, box-art comment headers, generic constraints and defaults, multi-line call continuations, dangling else binding, {$REGION} blank lines and alignment groups broken by a directive — so every page shows its own options working on real code.
The line-break master is now a regular .editorconfig key
"Keep user line breaks" can be set from a project's .editorconfig (delphikeepuser_linebreaks) like every other option, so a team can pin its line-break policy in the repository and an exported profile round-trips completely. This deliberately reverses the earlier safety stance that a repository file must never enable reflow.
Export writes what you see
Both export buttons (.config and .editorconfig) now write the values currently set in the options dialog, including pages you have edited but not yet applied — previously they silently wrote the last saved state, and the note explaining that is gone along with the behaviour.
The installer now serves both IDEs
The setup installs the 32-bit and the 64-bit design package side by side and registers each with its IDE (Known Packages / Known Packages x64) — Ctrl+D works in whichever RAD Studio 13 you launch. Both packages always carry the same version number; the installer build refuses to package mismatched builds.
Option: placement of the case-else line
A new switch "Indent else line" (delphiindentcase_else) controls where the else of a case statement sits: at the level of the alternative labels (default, unchanged behaviour) or at the case keyword — the classic RTL style. The statements below it remain governed by the existing case options.
3. Clarified and pinned
Blank lines around {$REGION}: both halves of the request are covered by existing options — set Max. consecutive blank lines to 2 to preserve a two-blank region spacing, and Blank lines around compiler directives (reflow tier) to insert them. Documented in the manual and locked in by tests. "Ensure final newline" and "Enable formatting": both reported against beta build 3.0.1; the current code base behaves correctly (the disable switch is honoured end to end, including the .editorconfig cascade, and the final-newline option is exact in both directions). Both behaviours are now locked in by tests. The 3.0.1 symptoms came from that build's Revive-based IDE integration, which the native RAD Studio 13.2 formatter interface has since replaced. Options pages opening slowly: verified not to be the plugin — even empty pages build up slowly; the cost sits in RAD Studio's own Tools/Options page embedding and would affect any add-in equally. The limits of conditional handling are documented
Two constellations the conditional-fork model cannot cover by principle now have their own "Known limits" manual section (German and English): include files that contribute block structure (the formatter refuses honestly or, for an included begin, may indent differently after the spot - never damaging anything), and directive regions starting or ending next to code on the same line (left untouched). Each comes with a minimal example, the observable behavior and an .editorconfig opt-out recipe.
4. By design
The options tree stays under the MSys node: requested twice from the community ("Third Party" placement); declined deliberately — the MSys node is the shared home for current and future MSys settings. Ctrl+D stays Ctrl+D: the format shortcut is not configurable — Ctrl+D has been the Delphi formatter's key since the built-in one, and on RAD Studio 13.2 the IDE owns the command anyway. Plugins that also claim Ctrl+D (e.g. GExperts) offer configurable shortcuts on their side.
5. Under the hood
The regression suite grew from 744 to 970 checks; every repository fixture is now also verified to format balanced, and formatting twice is now pinned for reflow profiles too, not just the default one. New shared conditional-directive classifier used by the re-indenter and the aligner; community-provided reproduction sources joined the fixture set. Several of the defects above were found by the fix for another one: the mid-line closer and the multiline-string cases came out of reviewing the if-expression reflow, and the .editorconfig cascade bug out of measuring the multiline-string fix.