From 9c695cb690ed5211169571ed3f8ee8d8c8178ce6 Mon Sep 17 00:00:00 2001 From: Nikolay Murzin Date: Sat, 6 Jun 2026 05:38:13 +0300 Subject: [PATCH 1/2] Add opt-in options for a compact multiline style Seven options, all defaulting to the current behavior so existing output and tests are unchanged (best used together, with BreakLinesMethod -> "LineBreakerV2"): - "KeepBindingsInline": decide the scoping/control break on the leading-line width (head + binding list / condition) rather than the max width, so a long body line does not force a short binding list / condition onto its own line. - "SpaceAfterControlOpener": `If[ cond,` so the condition aligns with branches. - "GlueAssignmentRHS": keep an assignment RHS on the operator line (`f := Block[{`), for top-level and inner Set / SetDelayed / UpSet / ... . - "TrailingCommas": comma at the end of a single-line element; on its own line only when an element is multiline. - "InlineShortControl": a control structure that fits on one line and whose branches are all single-line stays inline (`If[a, b, c]`). - "SpaceAfterPrefixNot": `! cond`. - "SpaceAroundPatternOperators": space Pattern (:), Optional (:), and PatternTest (?) like ordinary operators; MessageName (::) and Power (^) stay tight. Co-Authored-By: Claude Opus 4.8 (1M context) --- CodeFormatter/Kernel/CodeFormatter.wl | 96 +++++++++++++++++++ CodeFormatter/Kernel/Indent.wl | 131 +++++++++++++++++++++++--- 2 files changed, 213 insertions(+), 14 deletions(-) diff --git a/CodeFormatter/Kernel/CodeFormatter.wl b/CodeFormatter/Kernel/CodeFormatter.wl index 228e7f1..9604ff5 100644 --- a/CodeFormatter/Kernel/CodeFormatter.wl +++ b/CodeFormatter/Kernel/CodeFormatter.wl @@ -195,6 +195,94 @@ Options[CodeFormat] = { "NewlinesInGroups" -> Automatic, "NewlinesInScoping" -> Automatic, + (* + When deciding whether a scoping (Module/Block/With/Function) or control + (If/Switch/Which/...) construct must break to its fully-exploded form, look + at the width of the *leading* line (the head plus the binding list or + condition, e.g. `Block[{a, b},`) rather than the maximum width of the whole + construct. With this True the binding list / condition stays on the opener + line whenever that line fits, even if a body line is long: + + Block[{a, b}, + aLongBodyLineThatExceedsTheLineWidth[...] + ] + + When False (the default) a long body line forces the bindings onto their own + exploded lines. + *) + "KeepBindingsInline" -> False, + + (* + When True, a space is inserted after the opener of a multiline control + structure (If, Switch, Which, ...) so the condition lines up with the + branches below it: + + If[ cond, + then + , + else + ] + + Defaults to False (no space). + *) + "SpaceAfterControlOpener" -> False, + + (* + When True, the RHS of a top-level assignment is kept on the operator line + instead of breaking after the operator: + + f[x] := Block[{a, b}, (* True *) + body + ] + + f[x] := (* False, the default *) + Block[{a, b}, + body + ] + + Defaults to False. + *) + "GlueAssignmentRHS" -> False, + + (* + When True, a comma separating single-line elements is kept at the end of the + element's line: + + { + a, + b, + c + } + + rather than on its own line. When an element is itself multiline, the comma + still goes on its own line so it reads as an element boundary. Defaults to + False. + *) + "TrailingCommas" -> False, + + (* + When True, a control structure (If/Switch/Which/...) whose arguments are all + single-line and which fits within LineWidth is kept on one line instead of + always being broken across lines (`If[a, b, c]`). When a branch is itself + multiline, or the one-line form would exceed LineWidth, the structure is + broken as usual. Defaults to False (the traditional always-break behavior). + *) + "InlineShortControl" -> False, + + (* + When True, a space is inserted after a prefix Not, so `!cond` becomes + `! cond`. Defaults to False. + *) + "SpaceAfterPrefixNot" -> False, + + (* + When True, the pattern operators Pattern (:), Optional (:), and PatternTest + (?) are spaced like ordinary operators (`name : pat`, `x_ ? NumericQ`) instead + of being kept tight. MessageName (::) and Power (^) remain tight. Defaults to + False. + *) + "SpaceAroundPatternOperators" -> False, + (* Undocumented options *) @@ -440,6 +528,14 @@ Module[{ style["NewlinesInGroups"] = OptionValue["NewlinesInGroups"]; style["NewlinesInScoping"] = OptionValue["NewlinesInScoping"]; + style["KeepBindingsInline"] = OptionValue["KeepBindingsInline"]; + style["SpaceAfterControlOpener"] = OptionValue["SpaceAfterControlOpener"]; + style["GlueAssignmentRHS"] = OptionValue["GlueAssignmentRHS"]; + style["TrailingCommas"] = OptionValue["TrailingCommas"]; + style["InlineShortControl"] = OptionValue["InlineShortControl"]; + style["SpaceAfterPrefixNot"] = OptionValue["SpaceAfterPrefixNot"]; + style["SpaceAroundPatternOperators"] = OptionValue["SpaceAroundPatternOperators"]; + airiness = OptionValue[Airiness]; If[airiness === Automatic, airiness = 0.0 diff --git a/CodeFormatter/Kernel/Indent.wl b/CodeFormatter/Kernel/Indent.wl index 15aab50..df43257 100644 --- a/CodeFormatter/Kernel/Indent.wl +++ b/CodeFormatter/Kernel/Indent.wl @@ -582,6 +582,7 @@ Module[{indentedRator, indentedRandSeq, indentedRand, indentedRandMultiline, children = Flatten[{ indentedRator, + If[tag === Not && TrueQ[$CurrentStyle["SpaceAfterPrefixNot"]], space[], Nothing], indentedRandSeq, indentedRand }]; @@ -823,7 +824,18 @@ Module[{aggs, ratorsPat, split, definitelyDelete, definitelyInsert, definitelyAu Which[ TrueQ[definitelyInsert], - split = {#}& /@ indentedGraphs; + (* + With "TrailingCommas" -> True, single-line elements keep the comma at + the end of the element line (a, b, c) rather than on its own line; when + any element is itself multiline the comma stays on its own line so it + reads as an element boundary. + *) + split = + If[TrueQ[$CurrentStyle["TrailingCommas"]] && !TrueQ[anyIndentedGraphsMultiline], + Split[indentedGraphs, MatchQ[#2, ratorsPat]&] + , + {#}& /@ indentedGraphs + ]; , TrueQ[definitelyDelete], split = {indentedGraphs}; @@ -900,8 +912,16 @@ a^b 10^-9 *) -indentInfixRatorSurroundedByLeafs[Pattern | Optional | PatternTest | MessageName | Power][rator_] := - rator +(* +Pattern (:), Optional (:), and PatternTest (?) are tight by default, but with +"SpaceAroundPatternOperators" -> True they are spaced like ordinary operators +(name : pat, x_ ? NumericQ). MessageName (::) and Power (^) stay tight either way. +*) +indentInfixRatorSurroundedByLeafs[tag : Pattern | Optional | PatternTest | MessageName | Power][rator_] := + If[TrueQ[$CurrentStyle["SpaceAroundPatternOperators"]] && MatchQ[tag, Pattern | Optional | PatternTest], + indentInfixRator[tag][rator], + rator + ] indentInfixRatorSurroundedByLeafs[tag_][rator_] := indentInfixRator[tag][rator] @@ -1118,7 +1138,7 @@ Module[{aggs, rators, ratorsPat, split, Always line break after last rator, so redo newlines *) - MemberQ[$SpecialBreakAfterLastRator, tag] && $Toplevel, + (MemberQ[$SpecialBreakAfterLastRator, tag] && $Toplevel) || (TrueQ[$CurrentStyle["GlueAssignmentRHS"]] && MemberQ[{Set, SetDelayed, UpSet, UpSetDelayed, TagSet, TagSetDelayed}, tag]), (* lastRator = rators[[-1]]; lastRatorPos = Position[indentedGraphs, lastRator][[1]]; split = TakeDrop[indentedGraphs, lastRatorPos[[1]]]; *) @@ -1177,9 +1197,18 @@ Module[{aggs, rators, ratorsPat, split, , TrueQ[definitelyAutomatic], Which[ - MemberQ[$SpecialBreakAfterLastRator, tag] && $Toplevel, - blockAfterLastRator @ + (MemberQ[$SpecialBreakAfterLastRator, tag] && $Toplevel) || (TrueQ[$CurrentStyle["GlueAssignmentRHS"]] && MemberQ[{Set, SetDelayed, UpSet, UpSetDelayed, TagSet, TagSetDelayed}, tag]), + If[TrueQ[$CurrentStyle["GlueAssignmentRHS"]], + (* + Keep the RHS head on the operator line (f[x] := Block[{...},) instead + of breaking after the operator (f[x] :=\n Block[...]). The RHS node + supplies its own body indentation, so do not increment here. + *) baseOperatorNodeIndent[type, tag, data, split, ratorsPat, anyIndentedGraphsMultiline, infixRatorSurroundedBy] + , + blockAfterLastRator @ + baseOperatorNodeIndent[type, tag, data, split, ratorsPat, anyIndentedGraphsMultiline, infixRatorSurroundedBy] + ] (* $Toplevel, so do not re-format if exceeding LineWidth, cannot re-indent safely at $Toplevel *) @@ -1596,7 +1625,7 @@ Module[{indentedHead, indentedCommaNode, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInScoping" -> Insert *) @@ -1711,7 +1740,7 @@ Module[{indentedHead, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInScoping" -> Insert *) @@ -1773,6 +1802,18 @@ Module[{indentedHead, indentedCommaNode, commaChildren, groupChildren, child, commaExtent, groupExtent, extent}, + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -1850,6 +1891,7 @@ Module[{indentedHead, indentedCommaNode, groupChildren = Flatten[{ indent[opener], + If[TrueQ[$CurrentStyle["SpaceAfterControlOpener"]], space[], Nothing], indent /@ {openerSeq}, blockAfterFirstRator @ InfixNode[Comma, @@ -1873,7 +1915,7 @@ Module[{indentedHead, indentedCommaNode, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) @@ -1908,6 +1950,18 @@ Module[{indentedTest, indentedTestSeq, indentedComma1, indentedVal, indentedValS indentedVal = indent[val]; indentedValSeq = indent /@ {valSeq}; + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -1965,7 +2019,7 @@ Module[{indentedTest, indentedTestSeq, indentedComma1, indentedVal, indentedValS extent = computeExtent[children]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) @@ -2019,6 +2073,18 @@ Module[{indentedHead, indentedCommaNode, commaChildren, groupChildren, child, commaExtent, groupExtent, extent}, + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -2091,6 +2157,7 @@ Module[{indentedHead, indentedCommaNode, groupChildren = Flatten[{ indent[opener], + If[TrueQ[$CurrentStyle["SpaceAfterControlOpener"]], space[], Nothing], indent[Insert[#, EndOfLine -> True, {3, 1}]]& /@ {openerSeq}, block @ { InfixNode[Comma, @@ -2115,7 +2182,7 @@ Module[{indentedHead, indentedCommaNode, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) @@ -2175,6 +2242,18 @@ Module[{indentedHead, indentedCommaNode, commaChildren, groupChildren, child, commaExtent, groupExtent, extent}, + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -2284,7 +2363,7 @@ Module[{indentedHead, indentedCommaNode, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) @@ -2342,6 +2421,18 @@ Module[{indentedHead, commaChildren, groupChildren, child, commaExtent, groupExtent, extent}, + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -2398,7 +2489,7 @@ Module[{indentedHead, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) @@ -2501,6 +2592,18 @@ Module[{indentedHead, commaChildren, groupChildren, child, commaExtent, groupExtent, extent}, + (* + InlineShortControl: if the whole control structure fits on one line and no + branch is itself multiline, keep it inline rather than always breaking. + *) + If[TrueQ[$CurrentStyle["InlineShortControl"]] && MatchQ[node, CallNode[_, _, _]], + With[{inlined = commonCallNodeIndent[node]}, + If[!TrueQ[Lookup[inlined[[3]], "Multiline", False]] && getExtent[inlined][[1]] < $CurrentStyle["LineWidth"], + Throw[inlined] + ] + ] + ]; + definitelyDelete = OptionValue["NewlinesInControl"] === Delete || $CurrentStyle["NewlinesInControl"] === Delete; definitelyInsert = OptionValue["NewlinesInControl"] === Insert || $CurrentStyle["NewlinesInControl"] === Insert; @@ -2557,7 +2660,7 @@ Module[{indentedHead, extent = computeExtent[indentedHead ~Join~ {child}]; ]; - If[extent[[1]] >= $CurrentStyle["LineWidth"], + If[extent[[If[TrueQ[$CurrentStyle["KeepBindingsInline"]], 3, 1]]] >= $CurrentStyle["LineWidth"], (* exceeding LineWidth, so re-indent with "NewlinesInControl" -> Insert *) From a90524b73a01f058133f91b9603ccc9ed089def3 Mon Sep 17 00:00:00 2001 From: Nikolay Murzin Date: Sat, 6 Jun 2026 05:38:13 +0300 Subject: [PATCH 2/2] Add GuideStyle.mt tests for the compact multiline options 12 tests covering each option (KeepBindingsInline on/off, SpaceAfterControlOpener, GlueAssignmentRHS for Set/SetDelayed and inner assignments, TrailingCommas single-line and multiline-boundary, InlineShortControl, SpaceAfterPrefixNot, SpaceAroundPatternOperators) and a combined case. The existing suite is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- Tests/GuideStyle.mt | 215 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 Tests/GuideStyle.mt diff --git a/Tests/GuideStyle.mt b/Tests/GuideStyle.mt new file mode 100644 index 0000000..6bda3d9 --- /dev/null +++ b/Tests/GuideStyle.mt @@ -0,0 +1,215 @@ + +(* +Tests for the compact multiline ("GUIDE") conventions: + + - the scoping/control leading-width fix (default behavior) + - "SpaceAfterControlOpener" + - "GlueAssignmentRHS" (SetDelayed and Set) + - "TrailingCommas" + +All use BreakLinesMethod -> "LineBreakerV2". +*) + +Needs["CodeFormatter`"] + + +(* +KeepBindingsInline: a long (unbreakable) body line no longer drags the binding +list onto its own exploded line; the binding list stays on the Block[ line. +*) +Test[ + CodeFormat["Block[{a, b}, aFunctionCallWithOneVeryLongUnbreakableArgumentNameRightHere[xyz]]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 50, "KeepBindingsInline" -> True] + , +"Block[{a, b}, + aFunctionCallWithOneVeryLongUnbreakableArgumentNameRightHere[xyz] +]" + , + TestID->"GuideStyle-KeepBindingsInline" +] + + +(* +Default (KeepBindingsInline -> False): the same long body forces the bindings +onto their own exploded lines. +*) +Test[ + CodeFormat["Block[{a, b}, aFunctionCallWithOneVeryLongUnbreakableArgumentNameRightHere[xyz]]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 50] + , +"Block[ + {a, b} + , + aFunctionCallWithOneVeryLongUnbreakableArgumentNameRightHere[xyz] +]" + , + TestID->"GuideStyle-KeepBindingsInline-DefaultOff" +] + + +(* +SpaceAfterControlOpener: a space after the opener of a multiline control +structure, so the condition lines up with the branches. +*) +Test[ + CodeFormat["If[cond, thenValue, elseValue]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 20, "SpaceAfterControlOpener" -> True] + , +"If[ cond, + thenValue + , + elseValue +]" + , + TestID->"GuideStyle-SpaceAfterControlOpener" +] + + +(* +GlueAssignmentRHS keeps the RHS of a top-level SetDelayed on the operator line. +*) +Test[ + CodeFormat["f[x_] := Module[{a}, stmtOne; stmtTwo; stmtThree]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 25, "GlueAssignmentRHS" -> True] + , +"f[x_] := Module[{a}, + stmtOne; + stmtTwo; + stmtThree +]" + , + TestID->"GuideStyle-GlueAssignmentRHS-SetDelayed" +] + + +(* +GlueAssignmentRHS also covers Set (=). With TrailingCommas the single-line +association entries keep the comma at the end of the line. +*) +Test[ + CodeFormat["x = <|\"a\" -> 1, \"b\" -> 2, \"c\" -> 3|>", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 18, + "GlueAssignmentRHS" -> True, "TrailingCommas" -> True] + , +"x = <| + \"a\" -> 1, + \"b\" -> 2, + \"c\" -> 3 +|>" + , + TestID->"GuideStyle-GlueAssignmentRHS-Set-TrailingCommas" +] + + +(* +TrailingCommas: single-line elements keep the comma at the end of the line. +*) +Test[ + CodeFormat["{alpha, beta, gamma, delta}", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 12, "TrailingCommas" -> True] + , +"{ + alpha, + beta, + gamma, + delta +}" + , + TestID->"GuideStyle-TrailingCommas-SingleLine" +] + + +(* +TrailingCommas: when an element is itself multiline, the separating comma still +goes on its own line so it reads as an element boundary. +*) +Test[ + CodeFormat["{Block[{x}, bodyExprThatIsLong[x]], shortElement}", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 30, "TrailingCommas" -> True] + , +"{ + Block[{x}, + bodyExprThatIsLong[x] + ] + , + shortElement +}" + , + TestID->"GuideStyle-TrailingCommas-MultilineBoundary" +] + + +(* +All four conventions together. +*) +Test[ + CodeFormat["run[dir_String] := Block[{cmd, res}, If[!FileExistsQ[dir], Return[<|\"code\" -> 1, \"err\" -> \"missing\"|>]]; res = exec[cmd]; res]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 60, "KeepBindingsInline" -> True, + "SpaceAfterControlOpener" -> True, "GlueAssignmentRHS" -> True, "TrailingCommas" -> True] + , +"run[dir_String] := Block[{cmd, res}, + If[ !FileExistsQ[dir], + Return[<|\"code\" -> 1, \"err\" -> \"missing\"|>] + ]; + res = exec[cmd]; + res +]" + , + TestID->"GuideStyle-Combined" +] + + +(* +InlineShortControl: a control structure that fits on one line stays inline. +*) +Test[ + CodeFormat["If[a, b, c]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 40, "InlineShortControl" -> True] + , +"If[a, b, c]" + , + TestID->"GuideStyle-InlineShortControl" +] + + +(* +SpaceAfterPrefixNot: a space after a prefix Not. +*) +Test[ + CodeFormat["g[x_] := f[!x]", + "BreakLinesMethod" -> "LineBreakerV2", "SpaceAfterPrefixNot" -> True, "GlueAssignmentRHS" -> True] + , +"g[x_] := f[! x]" + , + TestID->"GuideStyle-SpaceAfterPrefixNot" +] + + +(* +SpaceAroundPatternOperators: PatternTest (?) and Pattern (:) are spaced; +MessageName (::) and Power (^) stay tight. +*) +Test[ + CodeFormat["f[w_?NumericQ] := w", + "BreakLinesMethod" -> "LineBreakerV2", "SpaceAroundPatternOperators" -> True, "GlueAssignmentRHS" -> True] + , +"f[w_ ? NumericQ] := w" + , + TestID->"GuideStyle-SpaceAroundPatternOperators" +] + + +(* +GlueAssignmentRHS also applies to inner (non-top-level) assignments. +*) +Test[ + CodeFormat["upd[s_] := Module[{s}, s[\"k\"] = <|\"a\" -> 1, \"b\" -> 2|>; s]", + "BreakLinesMethod" -> "LineBreakerV2", "LineWidth" -> 50, "GlueAssignmentRHS" -> True] + , +"upd[s_] := Module[{s}, + s[\"k\"] = <|\"a\" -> 1, \"b\" -> 2|>; + s +]" + , + TestID->"GuideStyle-GlueAssignmentRHS-Inner" +]