diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs
index 3fd8237..86b3df6 100644
--- a/CodeBlocker.Test/TemplateTests.cs
+++ b/CodeBlocker.Test/TemplateTests.cs
@@ -951,6 +951,82 @@ public void ABlankLineInsideABracedBodyIsKept()
Assert.AreEqual("public void Run()\n{\n\tFirst();\n\n\tSecond();\n}\n", Render(method));
}
+ [TestMethod]
+ public void ABracedBodyDropsTheBlankLinesAroundIt()
+ {
+ ClassTemplate type = new() { Name = "C" };
+ type.Members.Add(new MethodTemplate
+ {
+ Type = "void",
+ Name = "A",
+ BodyFactory = codeBlocker =>
+ {
+ using (new Scope(codeBlocker))
+ {
+ codeBlocker.WriteLine("x();");
+ }
+
+ codeBlocker.NewLine();
+ },
+ });
+ type.Members.Add(new MethodTemplate
+ {
+ Type = "void",
+ Name = "B",
+ BodyFactory = codeBlocker =>
+ {
+ codeBlocker.NewLine();
+ codeBlocker.WriteLine("{");
+ codeBlocker.WriteLine("}");
+ },
+ });
+
+ // One blank line between the members, and each opening brace directly under its signature.
+ Assert.AreEqual(
+ "class C\n{\n\tvoid A()\n\t{\n\t\tx();\n\t}\n\n\tvoid B()\n\t{\n\t}\n}\n",
+ Render(type));
+ }
+
+ [TestMethod]
+ public void ABracedBodyKeepsItsInteriorBlankLinesWhenItsOuterOnesAreDropped() =>
+ Assert.AreEqual(
+ "void Run()\n{\n\tFirst();\n\n\tSecond();\n}\n",
+ Render(new MethodTemplate
+ {
+ Type = "void",
+ Name = "Run",
+ BodyFactory = codeBlocker =>
+ {
+ codeBlocker.NewLine();
+ codeBlocker.NewLine();
+ using (new Scope(codeBlocker))
+ {
+ codeBlocker.WriteLine("First();");
+ codeBlocker.NewLine();
+ codeBlocker.WriteLine("Second();");
+ }
+
+ codeBlocker.NewLine();
+ codeBlocker.NewLine();
+ },
+ }));
+
+ [TestMethod]
+ public void ACommentOnlyBodyDropsTheBlankLinesAroundIt() =>
+ Assert.AreEqual(
+ "void Reset()\n{\n\t// nothing to reset\n}\n",
+ Render(new MethodTemplate
+ {
+ Type = "void",
+ Name = "Reset",
+ BodyFactory = codeBlocker =>
+ {
+ codeBlocker.NewLine();
+ codeBlocker.WriteLine("// nothing to reset");
+ codeBlocker.NewLine();
+ },
+ }));
+
[TestMethod]
public void AMultiLineBodyIsIndentedToWhereItIsSpliced()
{
diff --git a/CodeBlocker/Templates/TemplateRendering.cs b/CodeBlocker/Templates/TemplateRendering.cs
index f993ece..c95bb2d 100644
--- a/CodeBlocker/Templates/TemplateRendering.cs
+++ b/CodeBlocker/Templates/TemplateRendering.cs
@@ -102,9 +102,18 @@ internal static string[] SplitLines(CodeBlocker parent, string fragment)
/// would land at the current indent and every following line would keep the indentation it had
/// in its own buffer, which is why a nested body used to come out flush against the left margin.
///
- internal static void SpliceFragment(CodeBlocker parent, string fragment)
+ internal static void SpliceFragment(CodeBlocker parent, string fragment) =>
+ SpliceLines(parent, SplitLines(parent, fragment));
+
+ ///
+ /// Writes already-split lines at the parent's current indent, as does
+ /// for a whole fragment.
+ ///
+ /// The to write the lines to.
+ /// The lines, as returns them.
+ private static void SpliceLines(CodeBlocker parent, IEnumerable lines)
{
- foreach (string line in SplitLines(parent, fragment))
+ foreach (string line in lines)
{
if (line.Length == 0)
{
@@ -279,7 +288,12 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod
}
string body = RenderFragment(codeBlocker, bodyFactory);
- string[] lines = SplitLines(codeBlocker, body);
+
+ // Blank lines before the first and after the last line of the body are spacing the factory
+ // left around it, not part of it: spliced, a leading one would separate the declaration from
+ // its opening brace and a trailing one would add to the blank line between members. Blank
+ // lines inside the body are kept.
+ string[] lines = TrimBlankLines(SplitLines(codeBlocker, body));
// Blank lines carry no body of their own, so they are left out when deciding its shape. A
// factory that only wrote spacing, such as a NewLine after a loop over an empty collection,
@@ -302,7 +316,7 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod
codeBlocker.WriteLine();
using (new Scope(codeBlocker))
{
- SpliceFragment(codeBlocker, body);
+ SpliceLines(codeBlocker, lines);
}
return;
@@ -318,7 +332,24 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod
// A braced body starts on the line after the declaration.
codeBlocker.WriteLine();
- SpliceFragment(codeBlocker, body);
+ SpliceLines(codeBlocker, lines);
+ }
+
+ ///
+ /// Drops the empty lines at the start and end of a body's lines, keeping those between them.
+ ///
+ /// The lines, as returns them.
+ /// The lines from the first non-empty one to the last, or none if every line is empty.
+ private static string[] TrimBlankLines(string[] lines)
+ {
+ int first = Array.FindIndex(lines, line => line.Length != 0);
+ if (first < 0)
+ {
+ return [];
+ }
+
+ int last = Array.FindLastIndex(lines, line => line.Length != 0);
+ return [.. lines.Skip(first).Take(last - first + 1)];
}
///