diff --git a/CodeBlocker.Test/CodeBlockerTests.cs b/CodeBlocker.Test/CodeBlockerTests.cs index 6ae4b06..40f71af 100644 --- a/CodeBlocker.Test/CodeBlockerTests.cs +++ b/CodeBlocker.Test/CodeBlockerTests.cs @@ -71,6 +71,36 @@ public void NewLineShouldAddEmptyLine() Assert.AreEqual(CodeBlocker.DefaultNewLineString, result); } + [TestMethod] + public void NewLineAfterAPartialWriteKeepsTheNextLineIndented() + { + using CodeBlocker codeBlocker = CodeBlocker.Create(); + + codeBlocker.WriteLine("class C"); + using (new Scope(codeBlocker)) + { + codeBlocker.Write("int x = 1;"); + codeBlocker.NewLine(); + codeBlocker.WriteLine("int y = 2;"); + } + + Assert.AreEqual("class C\n{\n\tint x = 1;\n\tint y = 2;\n}\n", codeBlocker.ToString()); + } + + [TestMethod] + public void NewLineAtTheStartOfALineStillWritesNoIndentation() + { + using CodeBlocker codeBlocker = CodeBlocker.Create(); + + codeBlocker.Indent(); + codeBlocker.Write("a"); + codeBlocker.NewLine(); + codeBlocker.NewLine(); + codeBlocker.WriteLine("b"); + + Assert.AreEqual("\ta\n\n\tb\n", codeBlocker.ToString()); + } + [TestMethod] public void WriteLineWithIndentationShouldRespectIndentLevel() { diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs index ce27649..96b6150 100644 --- a/CodeBlocker.Test/TemplateTests.cs +++ b/CodeBlocker.Test/TemplateTests.cs @@ -356,6 +356,35 @@ public void AMultiLineExpressionBodyKeepsABlankLineWithoutIndentingIt() Render(type)); } + [TestMethod] + public void AnExpressionBodyStartingWithABlankLineKeepsItsFirstLineOnTheDeclaration() + { + ClassTemplate type = new() + { + Name = "C", + Keywords = { "public" }, + Members = + { + new PropertyTemplate + { + Type = "int", + Name = "X", + Keywords = { "public" }, + ExpressionBodyFactory = codeBlocker => + { + codeBlocker.NewLine(); + codeBlocker.WriteLine("1 +"); + codeBlocker.Write("2"); + }, + }, + }, + }; + + Assert.AreEqual( + "public class C\n{\n\tpublic int X => 1 +\n\t2;\n}\n", + Render(type)); + } + [TestMethod] public void AnEmptyExpressionBodyStillTerminatesTheDeclaration() { diff --git a/CodeBlocker/CodeBlocker.cs b/CodeBlocker/CodeBlocker.cs index d27313c..0d3a873 100644 --- a/CodeBlocker/CodeBlocker.cs +++ b/CodeBlocker/CodeBlocker.cs @@ -30,6 +30,16 @@ public class CodeBlocker : IDisposable private bool disposedValue; private bool shouldDisposeWriter; + /// + /// Whether the current line has been started by and not yet terminated. + /// + /// + /// needs this: keeps a + /// blank line free of indentation, but it does not re-arm the writer's pending-tab flag, so using + /// it to end a line that is already under way would leave the next line at column zero. + /// + private bool isMidLine; + private IndentedTextWriter IndentedTextWriter { get; } /// @@ -203,24 +213,57 @@ public static CodeBlocker Create(string indentString, string newLineString) /// /// Write a line of code without indentation. /// - public void NewLine() => IndentedTextWriter.WriteLineNoTabs(string.Empty); + /// + /// At the start of a line this writes a blank line with no indentation. After a partial + /// it ends that line instead, and the next line is indented as usual. + /// + public void NewLine() + { + if (isMidLine) + { + // Nothing is pending, so WriteLine() writes no tabs here, but it does re-arm them for + // whatever is written next. + IndentedTextWriter.WriteLine(); + } + else + { + IndentedTextWriter.WriteLineNoTabs(string.Empty); + } + + isMidLine = false; + } /// /// Write a line of code with indentation. /// /// The line of code to write. - public void WriteLine(string line) => IndentedTextWriter.WriteLine(line); + public void WriteLine(string line) + { + IndentedTextWriter.WriteLine(line); + isMidLine = false; + } /// /// Write a line of code with indentation. /// - public void WriteLine() => IndentedTextWriter.WriteLine(); + public void WriteLine() + { + IndentedTextWriter.WriteLine(); + isMidLine = false; + } /// /// Write a line of code with indentation. /// /// The text to write. - public void Write(string text) => IndentedTextWriter.Write(text); + public void Write(string text) + { + IndentedTextWriter.Write(text); + if (!string.IsNullOrEmpty(text)) + { + isMidLine = true; + } + } /// /// Increase the indentation level. diff --git a/CodeBlocker/Templates/TemplateRendering.cs b/CodeBlocker/Templates/TemplateRendering.cs index 533efac..f1e54ed 100644 --- a/CodeBlocker/Templates/TemplateRendering.cs +++ b/CodeBlocker/Templates/TemplateRendering.cs @@ -184,9 +184,7 @@ internal static void WriteConstraints(CodeBlocker codeBlocker, IReadOnlyCollecti return; } - // Terminate the declaration line the constraints hang off. WriteLine() rather than - // NewLine(): NewLine() writes through IndentedTextWriter.WriteLineNoTabs, which does not - // re-arm the writer's pending-tab flag, so whatever came next would land at column zero. + // Terminate the declaration line the constraints hang off. codeBlocker.WriteLine(); using IndentScope indent = new(codeBlocker); @@ -271,6 +269,14 @@ internal static void WriteExpressionBody(CodeBlocker codeBlocker, Action "); if (last < 0) { @@ -278,7 +284,7 @@ internal static void WriteExpressionBody(CodeBlocker codeBlocker, Action