Skip to content

Commit 60ab0d9

Browse files
xoofxCopilot
andcommitted
Fix include indentation after newline
Restore the caller newline state around include rendering so auto-indent still applies to the first line returned by an include after a preceding newline. Add sync and async regression tests for issue #662. Co-authored-by: Copilot <[email protected]>
1 parent e8b19f6 commit 60ab0d9

File tree

3 files changed

+64
-10
lines changed

3 files changed

+64
-10
lines changed

src/Scriban.Tests/TestIncludes.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,64 @@ This is a header
193193
TextAssert.AreEqual(expected, text);
194194
}
195195

196+
[Test]
197+
public void TestIncludeShouldIndentFirstLineAfterNewLineInAutoIndentedBlock()
198+
{
199+
var template = Template.Parse("""
200+
namespace Demo {
201+
{{
202+
"\n"
203+
include 'multilines'
204+
-}}
205+
}
206+
""");
207+
var context = new TemplateContext
208+
{
209+
TemplateLoader = new CustomTemplateLoader(),
210+
AutoIndent = true
211+
};
212+
213+
var text = template.Render(context).Replace("\r\n", "\n");
214+
var expected = """
215+
namespace Demo {
216+
217+
Line 1
218+
Line 2
219+
Line 3}
220+
""".Replace("\r\n", "\n");
221+
222+
TextAssert.AreEqual(expected, text);
223+
}
224+
225+
[Test]
226+
public async Task TestIncludeShouldIndentFirstLineAfterNewLineInAutoIndentedBlock_Async()
227+
{
228+
var template = Template.Parse("""
229+
namespace Demo {
230+
{{
231+
"\n"
232+
include 'multilines'
233+
-}}
234+
}
235+
""");
236+
var context = new TemplateContext
237+
{
238+
TemplateLoader = new CustomTemplateLoader(),
239+
AutoIndent = true
240+
};
241+
242+
var text = (await template.RenderAsync(context)).Replace("\r\n", "\n");
243+
var expected = """
244+
namespace Demo {
245+
246+
Line 1
247+
Line 2
248+
Line 3}
249+
""".Replace("\r\n", "\n");
250+
251+
TextAssert.AreEqual(expected, text);
252+
}
253+
196254
[TestCase(false)]
197255
[TestCase(true)]
198256
public void TestIncludeNamedArguments(bool strictVariables)

src/Scriban/ScribanAsync.generated.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ public async ValueTask<string> RenderTemplateAsync(Template template, ScriptArra
424424
var result = string.Empty;
425425
EnterRecursive(callerContext);
426426
var previousIndent = CurrentIndent;
427+
var previousTextWasNewLine = _previousTextWasNewLine;
427428
CurrentIndent = null;
428429
PushOutput();
429430
// Fetch any named argument values before pushing a new local scope, i.e. use the current context for evaluating the named arguments
@@ -445,20 +446,17 @@ public async ValueTask<string> RenderTemplateAsync(Template template, ScriptArra
445446
}
446447
if (previousIndent is not null)
447448
{
448-
// We reset before and after the fact that we have a new line
449+
// Isolate the included template output from the caller newline state.
449450
ResetPreviousNewLine();
450451
}
451452
result = await template.RenderAsync(this).ConfigureAwait(false);
452-
if (previousIndent is not null)
453-
{
454-
ResetPreviousNewLine();
455-
}
456453
}
457454
finally
458455
{
459456
PopLocal();
460457
PopOutput();
461458
CurrentIndent = previousIndent;
459+
_previousTextWasNewLine = previousTextWasNewLine;
462460
ExitRecursive(callerContext);
463461
}
464462
return result;

src/Scriban/TemplateContext.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,7 @@ public string RenderTemplate(Template template, ScriptArray arguments, ScriptNod
14431443
var result = string.Empty;
14441444
EnterRecursive(callerContext);
14451445
var previousIndent = CurrentIndent;
1446+
var previousTextWasNewLine = _previousTextWasNewLine;
14461447
CurrentIndent = null;
14471448
PushOutput();
14481449
// Fetch any named argument values before pushing a new local scope, i.e. use the current context for evaluating the named arguments
@@ -1464,20 +1465,17 @@ public string RenderTemplate(Template template, ScriptArray arguments, ScriptNod
14641465
}
14651466
if (previousIndent is not null)
14661467
{
1467-
// We reset before and after the fact that we have a new line
1468+
// Isolate the included template output from the caller newline state.
14681469
ResetPreviousNewLine();
14691470
}
14701471
result = template.Render(this);
1471-
if (previousIndent is not null)
1472-
{
1473-
ResetPreviousNewLine();
1474-
}
14751472
}
14761473
finally
14771474
{
14781475
PopLocal();
14791476
PopOutput();
14801477
CurrentIndent = previousIndent;
1478+
_previousTextWasNewLine = previousTextWasNewLine;
14811479
ExitRecursive(callerContext);
14821480
}
14831481
return result;

0 commit comments

Comments
 (0)