From 9bbed5f1c60aff5d8c34d1037b0447f79245d52c Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 14:24:44 +0000 Subject: [PATCH] Keep the next line indented when NewLine() ends a partial Write() [patch] NewLine() wrote through IndentedTextWriter.WriteLineNoTabs, which does not re-arm the pending-tab flag, so after a partial Write() the next line came out at column zero. CodeBlocker now tracks whether a line is under way and ends it with WriteLine() in that case, keeping WriteLineNoTabs for blank lines so they stay free of whitespace. WriteExpressionBody also skips leading blank lines, as it already skips trailing ones, so the arrow is no longer left dangling with a trailing space at the end of the declaration line. Fixes #115 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018fKEEvMM2dQpZfMjTCXhRL --- CodeBlocker.Test/CodeBlockerTests.cs | 30 +++++++++++++ CodeBlocker.Test/TemplateTests.cs | 29 ++++++++++++ CodeBlocker/CodeBlocker.cs | 51 ++++++++++++++++++++-- CodeBlocker/Templates/TemplateRendering.cs | 14 ++++-- 4 files changed, 116 insertions(+), 8 deletions(-) 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