- Version 1.2.0.24
- Dateigrösse 3.1MB
- Erstellungsdatum 3. Juli 2026
- Zuletzt aktualisiert 18. September 2026
-
Changelog
======================================================================= MSys Delphi Formatter 1.0.0.23 — What's New Maenken Systems · https://maenken.systems Release 1.0.0.23 (from release 0.7.0.22) =======================================================================
----------------------------------------------------------------------- 1. Fixed -----------------------------------------------------------------------
- A string of apostrophes ending a line no longer derails the
formatter. A literal like
''''— a regular string holding a single quote — may legally stand at the end of a line, for example in a wrapped concatenation. The formatter misread such a literal as the start of a multiline string: a file the compiler accepts was refused with a lexical error, and when the same shape appeared twice, everything between the two occurrences was silently treated as string content. Only an odd number of quotes opens a multiline string — the formatter now reads it that way too. - An
.editorconfigsection listing several masks in braces works. A section such as[{*.pas,*.dpr}]— a standard-conform way to address multiple file types — broke the settings resolution outright: on the command line every file under that.editorconfigwas skipped with an error, and in the IDE the format command went silently dead. Wildcards inside the braces are now understood exactly like outside them. - Alignment no longer pads statements after a
caseortryblock. The column aligner lost track of where executable code begins once acase,tryorrepeatblock closed, and from there on treated statements as declarations: with the initialization alignment switched on, a comparison likeOk := A = 1;gained stray spaces before its=. Declarations still align; statements are left alone again. array of record ... endkeeps its shape. The anonymous record body in a type likeTItems = array of record N: Integer; end;was not recognized: its fields fell out of the record, itsendclosed the surrounding type section instead, and every declaration after it dropped to the left margin — legal code, visibly torn apart. The same applies tofile of record. Both now open a proper body, and the metaclass formclass of Xstays untouched.- A
{$REGION}pair only pairs when it really is one. The column pairing introduced for region directives assumed every opener and closer stands alone on its line; an{$ENDREGION}written behind code, or a region inside anasmbody, silently derailed the bookkeeping and a later, unrelated closer inherited the wrong column. Unclean pairs are now left alone entirely — they simply follow the surrounding code, as they did before the pairing existed. - Alignment options work again after a
procedure ... of object;type. Such a procedure-type reference (and a generic constraint like) was mistaken for the start of a record body that never ends, so from there to the end of the unit the constant and type alignments went silent. Only real bodies count now. threadvarsections wrap likevarsections. The one-name-per-line option splitvarandconstname lists but silently skippedthreadvar, which declares exactly the same lists.- A stray
$with no digits stops the formatter. A bare dollar sign is not a hex literal and does not compile; the formatter used to format the file regardless. It now refuses it untouched, exactly as it does for an unterminated string. - The command line warns when the mirror folder overwrites
itself. With
--outand-rall results land flat in one folder; two same-named units from different subfolders used to overwrite each other without a word, so the mirror looked complete when it was not. Each collision now prints a warning naming both source files; the behaviour itself is unchanged. - The options preview spaces its tabs the way you set them. The two panes used a fixed tab width of their own, so a sample containing tabs was laid out differently from the code the formatter would actually produce — the preview contradicted the very setting it was there to demonstrate. It now follows the tab width currently being edited, so a freshly typed value takes effect at once, like every other option on those pages.
- A
{$REGION}and its{$ENDREGION}stay in the same column. The closer used to follow whatever the last line of code did, so a region wrapped around a routine whose body was indented ended up with its two halves two columns apart. A closer now follows its own opener — including the other way round, where a region inside a routine body moves along with it. Nested regions pair correctly, and a region written inside one branch of a conditional stays there. - The "indent else line" setting is remembered again.
It was written to the settings store but never read back, so switching
it off held until the next IDE start and then quietly returned. Anyone
who preferred the RTL-style
elseplacement had to set it again after every restart. Two settings that could not be carried in an exported profile at all — format on typing, and the alignment at the opening bracket — now travel with it, alongside the option that joins a wrapped assignment. - An inline variable written across two lines keeps its
indentation. With
varalone on one line and the declaration under it, the declaration was pulled back onto thevarcolumn — while the very same declaration under a routine'svarsection indents. Two spellings of one thing came out differently, and the one line the author had indented was the one that moved. The declaration now sits one level under its keyword, exactly where the routine section would put it; everything after the closing semicolon stays where it was. - Repeated formatting in the editor no longer adds a line to
the end of the file. Every run appended one empty line after
end., so a file that was already formatted still came back changed, and its end drifted further down with each pass. The cause sat at the boundary to the editor buffer rather than in the formatter itself: the editor reads the last line break of a file as a terminator when it loads the file, but as a separator when text is written back into it, so handing a buffer back unchanged still grew it by one line. The round trip is exact now — a file whose end the formatter does not touch comes back untouched, and a file that genuinely ends in a blank line keeps it. - A keyword used as a member name after a dot is no
longer treated as an operator.
class operator Vector.In(...)came out asclass operator Vector. in (...)— the qualified name pulled apart and the member lowercased. The same happened to.And,.Div,.Is,.As,.Orand.Not. Operator overloads are exactly the code where such names have to appear, so this hit real libraries. - A case label following an arm that ends in a nested
casekept the inner case's indentation instead of returning to the label column. Only the first label after the nested block was affected, which is why it read as a one-off rather than a pattern. - Nested inline
ifexpressions (Delphi 13) lost every level of indentation: eachthen/elseline fell back to the statement column, so a four-level expression came out as one flat block. Each level now carries one continuation step, withthenandelseof the sameifin one column. - A stray blank line was no longer left before the closing
keyword of an
initializationorfinalizationsection: with the corresponding option off — the default — the statements of such a section were re-indented onto the keyword's column instead of being left alone. The option is documented as "keep as is", and now it keeps. - A unary sign no longer fuses with the keyword in front of
it.
then -1,else -2,not -Yandto -1came out asthen-1,else-2,not-Yandto-1— legal, but they read as a single token. - A
class procedureorclass functionbody directly after auses,constorvarsection is no longer indented like a section entry. Only theclassprefix made the difference: a plainprocedurein the same spot closed the section and landed on the routine column, while a class method header was taken for one more entry of the section and pushed to the entries' column — even when it already stood at column 0, with the standard settings, and stable across runs, so reformatting never repaired it. A comment standing above such a header shared its fate. The section now ends at any routine header, prefixed or not. Reported by a user against a unit whose class-method implementations followed the implementationusesclause; measured against a large open source library, this moves the headers in twelve units back to the column their authors wrote. - A routine body that slipped a level comes back.
Comment out the
end;of a procedure, format, and everything below it moves one level in — as it should. Restore theend;and format again, and the routine headers came back to the margin, but theirbeginandendlines stayed where the slip had put them, stable across runs, so reformatting never repaired it. The rule that protects a nested routine's own column had been keeping the outer routine's frame too. It now keeps only what it was made for; thebegin, theendand a localvar,constortypeline are pulled back under their header again. Reported from the field with exactly that three-step sequence.
----------------------------------------------------------------------- 2. New -----------------------------------------------------------------------
- A blank-line separator can be told not to split what belongs
together. A separator guarantees a blank line in front of a
keyword without asking whether the line above is part of the same
thought. Two cases, and they point in opposite directions: a ruler
comment written directly over
implementationintroduces that section, and a localtype/var/constbelongs to the routine header above it — both got a blank line pushed into them. The new option leaves both alone. It is off by default, so nothing changes unless you ask for it, and a unit-leveltypefollowing a forward declaration keeps its blank line either way. Only profiles that have the separators running were ever affected.
----------------------------------------------------------------------- 3. Clarified and pinned -----------------------------------------------------------------------
- The statements of a
case-elsebranch now sit one level under their ownelseby default. Previously they stayed on the column of the other alternatives' statements, which left two levels betweenelseand the code belonging to it. The setting has existed for a while; only its default changed. It has an effect solely in combination with the RTL-styleelseplacement (the else line on thecasecolumn) — with the else line indented, both readings coincide and nothing moves. - The option for that indent has moved into the "Case
statements" group of the options dialog, where the rest of the
case settings live. It used to sit under "Declarations", three rows
away from anything to do with
case. - The help texts in the options dialog appear after one second instead of half a second. Crossing the page with the mouse no longer throws up help nobody asked for; a deliberate pause still shows it.
----------------------------------------------------------------------- 4. Under the hood -----------------------------------------------------------------------
- A deep pass over the engine consolidated duplicated internals: the
property specifier words, the operator list that recognizes
if-expressions and the synthesized-space factory each live in one shared place now, the re-indenter works on its own copy of the token stream instead of the caller's, and the command-line program calls the encoding round-trip unit directly. No behaviour change; the test suite grew by over a hundred checks pinning these and the fixes above.
======================================================================= MSys Delphi Formatter 0.7.0.22 — What's New Maenken Systems · https://maenken.systems Release 0.7.0.22 (from release 0.6.0.21) =======================================================================
----------------------------------------------------------------------- 1. New -----------------------------------------------------------------------
- Every option explains itself. Hovering an option anywhere in the settings dialog brings up its help: what the option does, a short example and the default — laid out for reading instead of squeezed into a single line of tooltip. All 90 options are covered, in English and German. It runs through the IDE's own hint mechanism, so it can never end up as a second popup competing with the usual one.
- The preview panes are syntax coloured and numbered. The Before and After panes used to paint their sample in one single colour. They now colour it per token — reserved words, strings, comments, numbers, directives — and the colours come from your own editor colour scheme, so the preview looks like the editor you actually work in. Both panes carry line numbers in a quiet gutter, which is what makes the two halves comparable: the same number on the left and on the right answers what the formatter did to that line. A scheme that cannot be read falls back to plain text on the correct background rather than to a guess.
- Collapse a line break after an assignment when the statement
fits. A new option on the Line Breaks page — off by default —
removes the break directly after an assignment's
:=if the whole statement then still fits within the right margin, soResult :=followed by a short expression on the next line becomes one line again. Like the end/else option it only ever merges: it never breaks a line, it only takes a break away. It stays out of the way where the layout was intentional — if the statement carries any further break, or the right-hand side is an anonymous method, it is left untouched. The statement is measured as though all its breaks were already gone, so the result does not depend on the current layout and reformatting again changes nothing. Requested by a user.
----------------------------------------------------------------------- 2. Fixed -----------------------------------------------------------------------
Five defects in this release share one trigger: they only appear once the reflow of line breaks is switched on, and the standard Delphi 12 configuration switches it on. Anyone who imported their existing settings met several of them at once, which is why the import had a reputation for producing odd results. The import itself turned out to be faithful — it was applying the options exactly as given, and the engine mishandled them.
- An inline variable is no longer mistaken for a goto
label. With Line break after a label active,
var a: Integer := 1;was torn in two after the colon, leavingvar a:on one line andInteger := 1;on the next. The rule recognised any identifier followed by a colon; it now requires what a label actually is — the start of a statement. Real labels still get their line break, andfor I: Integer := 0 to 3 dostays in one piece. - No more blank line before every inline variable.
With Blank lines before a subsection set, a blank line was
inserted ahead of any line starting with
var,constortype— including an inline variable in the middle of a routine. In code written for Delphi 10.3 and later that put a blank line between nearly every statement. The separator now fires only where a declaration section can actually begin; genuine sections keep their blank line. - Local declarations keep their indentation. Switching
on either of the two routine-body indentation options pushed a
routine's own
var,constandtypedeclarations to column 0 — actively, even when they had been correctly indented beforehand, while thebegin…endbody kept indenting normally. Both options now leave the declarations one step under their keyword, for top-level and nested routines alike. - Conditional branch-swap tails survive reflow. A tail
of the form
else{$ELSE}begin{$ENDIF}has to stay on one line — both passes of the engine depend on it. With Line break after begin active, thebeginwas separated from the directive behind it, the structural bookkeeping went astray, and the file ended up with unbalanced blocks. The formatter then refused it outright, so the affected units silently stayed unformatted. Measured against a large open source library, six units were affected; now none are. - Variant records after a nested section no longer shift the
rest of the file. When Indent type/const sections inside a
class or record body was on, a record variant part
(
case Integer ofwithout anendof its own) following a nestedvar,constortypesection left an open block behind. Every declaration after that record — up to and including the finalend.— stayed one level too deep and never returned to column 0. Because the result was stable across runs, reformatting never repaired it. - Inline variables in initialization and finalization are
statements again. Those sections carry statements but open no
block, so everything that asks "are we in executable code?" read them as
declaration territory. An inline
varthere was taken for a declaration section: it pushed every following statement one level deeper, attracted the blank-line separator meant for real sections, and had the name list ofvar a, b: Integer;split at its comma. All three are gone, in both passes. A related case: a declaration written across two lines —varalone, the name below it — was read as a goto label and pulled to the label column. - Directive lines follow their code instead of
freezing. With directive indentation switched off, a
{$IFDEF}line kept its original column literally. That was invisible until the block settings moved the code around it — then the directive stayed behind and ended up stranded between lines it no longer lined up with, which reads as if the conditional had not been understood at all. It now moves with the code, keeping exactly the distance the author gave it. Where nothing moves, nothing changes. - Anonymous methods get their own indentation level
again. An anonymous method passed as a call argument —
TThread.Queue(nil, procedure begin … end)and every shape like it — had its whole contents pulled onto the column of its ownbegin. Afororifinside it lost its level as well, and its localvar,constandtypedeclarations sat on the same column as the keyword introducing them. The body now sits one step under itsbegin, and the declarations one step under their section keyword, while thebeginitself stays where it belongs — attached to the call it is an argument of. This is not tied to an option: it happens with the standard settings, which is why it was reported from ordinary use. Measured against a large open source library, every one of the affected places now matches the column its authors had written by hand. - A unit named Inline keeps its name.
inlineis a directive in Delphi, not a reserved word — but the formatter had it listed among the reserved words, so every identifier of that name was treated as a keyword and re-cased:unit Inline;came out asunit inline;, and the same happened in a uses clause or a qualified reference. Nothing stopped compiling, since Delphi does not distinguish case in identifiers, but the formatter was changing a name that belongs to the author. It is now handled like every other directive —overload,stdcall,register,deprecatedwere all already treated correctly, so this removes an inconsistency rather than adding a rule. - One-line anonymous methods no longer make a whole unit
unformattable. Passing an anonymous method as a call argument on
a single line —
Foo(procedure begin X := 1; end)— left a stray internal region behind whenever one of the two routine-body indentation options was on. A single occurrence still balanced out by chance; from the second one in a file the bookkeeping came apart, the safety net declared the file unsound, and the formatter refused it. The visible symptom was the worst kind: nothing happened at all, with no explanation. Measured against a large open source library, this was the last file in 366 that could not be formatted under any of six configuration profiles; all of them are now clean. - A wrapped assignment keeps its indentation level.
When an assignment is broken across lines without brackets, everything
below the first line used to fall back to the column of the statement
itself, so
Result :=and the expression belonging to it ended up side by side and the statement read as two. The continuation now sits one step in, anchored on the line that opened the assignment — so a right-hand side spanning three or four lines stays at one step instead of drifting further right with every line. Where the right-hand side is an anonymous method, its header, its local declarations, its body and its end each land on the level they belong to. Brackets keep priority, so a wrapped argument list is unaffected. Measured against a large open source library, this moves the formatter closer to what the authors wrote by several hundred lines. - A for loop is no longer aligned like an assignment.
With Align assignments switched on, a loop header standing
between two assignments was padded into their column, so
for I := 0 to 9 docame out asfor I := 0 to 9 do. The alignment looks for any:=outside brackets, and a loop header's is spelled exactly like an assignment's — only the leading keyword tells them apart. It does now. - Comments no longer drift when a section ends. A
uses,varorconstsection ends at its semicolon — but the formatter kept treating the space after it as part of the section, so a comment standing between the section and the next routine was pulled in one level. That hit{ TMyClass }, the comment the IDE itself writes above the first method body: the formatter moved a line nobody had touched. Such a comment now takes the level of the code it introduces. A comment inside a section still moves with its declarations, and one above a nested routine follows that routine rather than dropping to the margin. Measured on a large code base, this restores the author's own column in the great majority of cases. Compiler directives on the same spot follow the same rule, and a closing{$ENDIF}moves together with the{$IF}it belongs to. - A note above a routine directive moves with it. The
option that indents a stand-alone
inline;oroverload;line under its routine header moved the directive alone. In the very shape the option was built for — a note explaining why the directive is wrapped in a condition — the comment stayed behind on the old column, so a pair that had been written on one column came apart, and the result looked less like the original than before the option existed. The comment now takes the column of the directive it introduces, the same rule a comment above anelsealready followed. A comment that introduces an ordinary declaration keeps its place, and Indent comments: No still means every comment stays exactly where it is. Measured against a large open source library, this moves one single line in 366 units — the one from the report. - The align page's preview demonstrates its own option
again. The sample there had no hand-set columns in it, so
switching Keep hand-set columns on changed nothing visible —
the one thing the preview exists for. The sample now carries a
hand-aligned
=column and hand-aligned trailing comments, which shows all three behaviours in one snippet: the default collapses them, the preserve option keeps them, and the align options re-compute them.
----------------------------------------------------------------------- 3. Under the hood -----------------------------------------------------------------------
- Every defect above is pinned by regression checks that fail without their respective fix — including the column drift, which no balance check could catch.
- Verified against a large open source code base — 366 units — under twelve different configuration profiles, not just the default one: no unbalanced unit, no file the formatter had to refuse, and under every one of the twelve a second pass over the formatted output changes nothing.
- The alignment options were measured against inline variables and leave them alone in every combination, at every nesting depth — that immunity is now pinned by a test as well.
======================================================================= MSys Delphi Formatter 0.6.0.21 — What's New Maenken Systems · https://maenken.systems Release 0.6.0.21 (from release 0.5.0.17) =======================================================================
----------------------------------------------------------------------- 1. New -----------------------------------------------------------------------
- Indent
type/constsections inside a class body. Many libraries — and the RTL itself — put atypeorconstsection inside a class or record body and indent its declarations one level under the keyword. The formatter used to pull them back to the level of the section keyword, and the existing "indent after sections" option did not help: that one only ever covered the unit sectionsinterfaceandimplementation. The new option Indent type/const sections inside a class or record body (Indentation page, or the.editorconfigkeydelphi_indent_nested_sections) covers the class-internal case. Default off, so nothing changes unless you ask for it; the indented region ends at the next visibility specifier, method declaration, further section keyword or the type'send, so it cannot spill into the rest of the file. - Indent case-else statements under their own else.
With the case-else placed on the
casecolumn, its statements used to stay on the arm column — two levels away from theelsethey belong to. The new option Indent case-else statements under their own else (Indentation page,.editorconfigkeydelphi_indent_case_else_contents) closes that gap. It only differs in exactly that pairing; with the default case-else placement both readings coincide. - Indent directive lines under their routine header. A
stand-alone
inline;,overload;orstdcall;line — including the conditional form{$IF …}inline;{$IFEND}— can now be indented one continuation step under the header it belongs to, via Indent directive lines under their routine header (Indentation page,.editorconfigkeydelphi_indent_routine_directives). This is a matter of style rather than a defect: the;after the parameter list ends the declaration, so the directive really is a line of its own and the flat reading is just as defensible — which is why the default is off. A comment between the header and the directive does not break the association. - Keep hand-set columns. Manually aligned trailing
comments and
=columns in const blocks used to be collapsed to a single space — the formatter threw away work someone had done on purpose. The align options were no answer: they compute the minimal column and so replace one normalisation with another. The new option Keep hand-set columns (Align page,.editorconfigkeydelphi_preserve_manual_columns) simply leaves such a run alone. It never widens a single space, so it cannot create alignment — it only keeps what is there. Default off.
======================================================================= MSys Delphi Formatter 0.5.0.17 — What's New Maenken Systems · https://maenken.systems Release 0.5.0.17 (from release 0.4.0.10) =======================================================================
All changes in this release were driven by community feedback from the Delphi-PRAXiS beta threads — thank you to everyone who tested and reported.
----------------------------------------------------------------------- 1. Fixed -----------------------------------------------------------------------
- The branch-swap conditional idiom no longer blocks
formatting. The pattern where one preprocessor branch opens a
case … ofand the other contributes abegininstead — both sharing the closingends, with{$ELSE}and{$ENDIF}sitting in the middle of one code line (else{$ELSE}begin{$ENDIF}) — used to make the whole file count as structurally unbalanced: with several occurrences the formatter refused the file (this is what made Spring4D'sSpring.pasappear "unformattable"), and with exactly one occurrence everything after the pattern silently drifted two columns to the right. The formatter now recognizes this shape, follows the first branch — consistent with how conditional regions are handled everywhere else — and keeps the indentation stable. Other mid-line directive regions remain deliberately untouched, as documented under Known limits. - The options dialog is readable again under a light IDE theme. The pane holding the options took its background from the editor colour scheme instead of the IDE theme. Since that scheme is chosen independently of the theme, anyone running a light IDE with a dark editor scheme — a very common combination — got a black options pane while the captions stayed dark, which left whole pages unreadable. Editor colours now dress the two preview panes only, where they belong; everything else follows the IDE theme, in both the light and the dark variant.
- The command-line formatter carries version
information.
Formatter.exeshipped without any version resource at all: neither Windows' file properties nor a deployment script could tell which build was in use. It now reports its version and vendor like the plugin does, and both are built from the same version number.
----------------------------------------------------------------------- 2. New -----------------------------------------------------------------------
- Blank-line separators without switching on reflow.
The separators that put blank lines at structural boundaries — between
methods, before the final
end., around sections — used to require the reflow master to be off, which meant that anyone who only wanted a blank line between methods had to hand the formatter their whole line layout as well. Inserting a blank line moves no code, so it never needed that: the new option Apply the separators even when user line breaks are kept (Blank Lines page, or the.editorconfigkeydelphi_apply_blank_line_separators) releases them on their own. The default is off, so nothing changes unless you ask for it. Verified against a large real-world code base: with the option on, the only difference to a normal run is blank lines — not a single token moves. - Product page, version and contact are now in plain sight. The options dialog carries a slim header above every page: the product name with a link straight to the product page — opening the English or the German edition to match the IDE language — and the installed version on the right. Beta testers told us the previous placement was easy to miss, which made it unnecessarily hard to find out where to send feedback.
- Format while typing (pilot, opt-in). On IDEs that
offer the native formatter interface — prepared for future RAD Studio
versions — the formatter can now re-format the line the caret leaves
with Enter — same rules and safety gates as Ctrl+D, but completely
silent: an incomplete or unbalanced buffer (the normal state while
typing) or an already-formatted line is a quiet no-op, and no dialog
ever appears. Caret, breakpoints and bookmarks follow exactly. Strictly
opt-in (default: off) via the new General-page option Format line on
Enter or per project via the new
.editorconfigkeydelphi_format_on_type; semicolons, pasting and navigation never trigger it. - The command-line formatter no longer overwrites files it
could not read properly. When the formatter cannot make sense
of a file's structure — it ends up with more block openers than closers
— its indentation is a guess. The IDE has always refused such a buffer
and left it alone; the command-line tool used to mention the fact in
passing and rewrite the file anyway, with a success exit code. A batch
run over a repository therefore wrote guessed indentation into exactly
the files the IDE protects, and
--checkreported them as an ordinary formatting difference. Both now give the same answer: the file is reported asKEEPand left untouched, the run ends with its own exit code 3 so a build pipeline can tell "needs formatting" from "cannot be formatted safely", and--outstill writes the file into the mirror directory, where it overwrites nothing. The new switch--best-effortrestores the previous behaviour for anyone who wants it, exit code included.
----------------------------------------------------------------------- 3. Under the hood -----------------------------------------------------------------------
- Conditional branches no longer add up in the line-break
pass. The reflow stage kept one structural tally across all
branches of a
{$IFDEF}region instead of following the first one, the way the indentation stage already did. A branch that opened or closed a block without closing or opening it again shifted that tally for the rest of the unit, after which whole families of line-break rules quietly stopped applying — or started applying — far away from the directive that caused it. The effect was invisible from the outside: the output stayed structurally sound and stable under repeated formatting, only the line breaks were placed as if a different file had been read. Both stages now follow the same rule. This only ever affected profiles with reflow enabled. - A
varorconstparameter is no longer mistaken for a declaration section. With per-element wrapping ofvar/constlists enabled, a routine taking avarparameter had the comma in a generic return type (IDictionary) broken as if it were a name-list separator, leaving the continuation dangling. The two meanings of the keyword are now kept apart.
======================================================================= MSys Delphi Formatter — History 0.4.0.10 =======================================================================
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: delphi_blank_lines_around_initialization
guarantees blank lines before and after the initialization/
finalization keywords, delphi_blank_lines_before_final_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
(delphi_keep_user_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" (delphi_indent_case_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
.editorconfigcascade, 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
.editorconfigcascade bug out of measuring the multiline-string fix.