Skip to content

Commit 7dee006

Browse files
authored
Attempt to stabilize whitespaces (#2211)
* Attempt to stabilize whitespaces * Fix test
1 parent 849a701 commit 7dee006

File tree

10 files changed

+78
-30
lines changed

10 files changed

+78
-30
lines changed

src/Elastic.Markdown/Helpers/DocumentationObjectPoolProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public static string UseLlmMarkdownRenderer<TContext>(BuildContext buildContext,
2828
try
2929
{
3030
action(subscription.LlmMarkdownRenderer, context);
31-
return subscription.RentedStringBuilder!.ToString();
31+
var result = subscription.RentedStringBuilder!.ToString();
32+
return result.EnsureTrimmed();
3233
}
3334
finally
3435
{
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace Elastic.Markdown.Helpers;
6+
7+
internal static class StringExtensions
8+
{
9+
/// <summary>
10+
/// Ensures the string is trimmed of leading and trailing whitespace.
11+
/// Only allocates a new string if trimming actually changes the content.
12+
/// </summary>
13+
/// <param name="value">The string to trim</param>
14+
/// <returns>The trimmed string, reusing the original if no changes needed</returns>
15+
public static string EnsureTrimmed(this string value)
16+
{
17+
var span = value.AsSpan();
18+
var trimmed = span.Trim();
19+
return trimmed.Length != value.Length ? trimmed.ToString() : value;
20+
}
21+
}

src/Elastic.Markdown/Myst/CodeBlocks/CodeViewModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public HtmlString RenderBlock()
2828
EnhancedCodeBlockHtmlRenderer.RenderCodeBlockLines(subscription.HtmlRenderer, EnhancedCodeBlock);
2929
var result = subscription.RentedStringBuilder?.ToString();
3030
DocumentationObjectPoolProvider.HtmlRendererPool.Return(subscription);
31-
return new HtmlString(result);
31+
return result == null
32+
? HtmlString.Empty
33+
: new HtmlString(result.EnsureTrimmed());
3234
}
3335

3436
public HtmlString RenderLineWithCallouts(string content, int lineNumber)

src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using Elastic.Documentation.AppliesTo;
77
using Elastic.Markdown.Diagnostics;
8+
using Elastic.Markdown.Helpers;
89
using Elastic.Markdown.Myst.Comments;
910
using Elastic.Markdown.Myst.Directives.AppliesTo;
1011
using Markdig.Helpers;
@@ -20,8 +21,11 @@ public class EnhancedCodeBlockHtmlRenderer : HtmlObjectRenderer<EnhancedCodeBloc
2021
private const int TabWidth = 4;
2122

2223
[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly")]
23-
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer) =>
24-
slice.RenderAsync(renderer.Writer).GetAwaiter().GetResult();
24+
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer)
25+
{
26+
var html = slice.RenderAsync().GetAwaiter().GetResult();
27+
_ = renderer.Write(html.EnsureTrimmed());
28+
}
2529

2630
/// <summary>
2731
/// Renders the code block lines while also removing the common indentation level.

src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics.CodeAnalysis;
66
using Elastic.Documentation.AppliesTo;
77
using Elastic.Markdown.Diagnostics;
8+
using Elastic.Markdown.Helpers;
89
using Elastic.Markdown.Myst.CodeBlocks;
910
using Elastic.Markdown.Myst.Directives.Admonition;
1011
using Elastic.Markdown.Myst.Directives.AppliesSwitch;
@@ -398,15 +399,21 @@ private static void WriteSettingsBlock(HtmlRenderer renderer, SettingsBlock bloc
398399
{
399400
var document = MarkdownParser.ParseMarkdownStringAsync(block.Build, block.Context, s, block.IncludeFrom, block.Context.YamlFrontMatter, MarkdownParser.Pipeline);
400401
var html = document.ToHtml(MarkdownParser.Pipeline);
401-
return html;
402+
403+
// Trim to ensure consistent whitespace
404+
return html.EnsureTrimmed();
402405
}
403406
});
404407
var html = slice.RenderAsync().GetAwaiter().GetResult();
405408
_ = renderer.Write(html);
406409
}
407410

408411
[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly")]
409-
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer) => slice.RenderAsync(renderer.Writer).GetAwaiter().GetResult();
412+
private static void RenderRazorSlice<T>(RazorSlice<T> slice, HtmlRenderer renderer)
413+
{
414+
var html = slice.RenderAsync().GetAwaiter().GetResult();
415+
_ = renderer.Write(html.EnsureTrimmed());
416+
}
410417

411418
[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly")]
412419
private static void RenderRazorSliceRawContent<T>(RazorSlice<T> slice, HtmlRenderer renderer, DirectiveBlock obj)

src/Elastic.Markdown/Myst/Directives/DirectiveViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ public HtmlString RenderBlock()
1818

1919
var result = subscription.RentedStringBuilder?.ToString();
2020
DocumentationObjectPoolProvider.HtmlRendererPool.Return(subscription);
21-
return new HtmlString(result);
21+
return result == null ? HtmlString.Empty : new HtmlString(result.EnsureTrimmed());
2222
}
2323
}

src/Elastic.Markdown/Myst/Directives/Stepper/StepViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,6 @@ public HtmlString RenderTitle()
8181

8282
var result = subscription.RentedStringBuilder?.ToString() ?? Title;
8383
DocumentationObjectPoolProvider.HtmlRendererPool.Return(subscription);
84-
return new(result);
84+
return new HtmlString(result.EnsureTrimmed());
8585
}
8686
}

src/Elastic.Markdown/Myst/Renderers/LlmMarkdown/LlmBlockRenderers.cs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ public static void RenderBlockWithIndentation(LlmMarkdownRenderer renderer, Mark
8686
return url;
8787
}
8888
}
89-
90-
91-
9289
}
9390

9491
/// <summary>
@@ -140,6 +137,7 @@ protected override void Write(LlmMarkdownRenderer renderer, EnhancedCodeBlock ob
140137
renderer.Write(obj.Caption);
141138
renderer.WriteLine(" -->");
142139
}
140+
143141
renderer.Write("```");
144142
if (!string.IsNullOrEmpty(obj.Language))
145143
renderer.Write(obj.Language);
@@ -151,6 +149,7 @@ protected override void Write(LlmMarkdownRenderer renderer, EnhancedCodeBlock ob
151149
renderer.Write(line.ToString());
152150
renderer.WriteLine();
153151
}
152+
154153
renderer.WriteLine("```");
155154
}
156155

@@ -170,32 +169,35 @@ public class LlmListRenderer : MarkdownObjectRenderer<LlmMarkdownRenderer, ListB
170169
{
171170
protected override void Write(LlmMarkdownRenderer renderer, ListBlock listBlock)
172171
{
173-
var baseIndent = CalculateNestedIndentation(listBlock);
174-
if (listBlock.Parent is not ListItemBlock)
172+
var isNestedList = listBlock.Parent is ListItemBlock;
173+
174+
// Nested lists don't need base indent - parent's continuation indent positions them correctly
175+
var baseIndent = isNestedList ? "" : CalculateNestedIndentation(listBlock);
176+
177+
// Top-level lists need block spacing before them
178+
if (!isNestedList)
175179
renderer.EnsureBlockSpacing();
176180

177181
var isOrdered = listBlock.IsOrdered;
178-
var itemIndex = 1;
179-
if (isOrdered && int.TryParse(listBlock.DefaultOrderedStart, out var startIndex))
180-
itemIndex = startIndex;
182+
var itemIndex = listBlock.IsOrdered && int.TryParse(listBlock.DefaultOrderedStart, out var startIndex)
183+
? startIndex
184+
: 1;
181185

182-
foreach (var item in listBlock.Cast<ListItemBlock>())
186+
var items = listBlock.Cast<ListItemBlock>().ToArray();
187+
foreach (var item in items)
183188
{
184189
renderer.Write(baseIndent);
185190
renderer.Write(isOrdered ? $"{itemIndex}. " : "- ");
186-
foreach (var block in item)
191+
for (var blockIndex = 0; blockIndex < item.Count; blockIndex++)
187192
{
188-
if (block != item.First())
193+
var block = item[blockIndex];
194+
if (blockIndex > 0)
189195
{
190-
var continuationIndent = GetContinuationIndent(baseIndent, isOrdered);
191-
renderer.Write(continuationIndent);
196+
renderer.EnsureLine();
197+
renderer.Write(GetContinuationIndent(baseIndent, isOrdered));
192198
}
193-
194-
if (block != item.First() && block is ListBlock)
195-
renderer.WriteLine();
196199
RenderBlockWithIndentation(renderer, block, baseIndent, isOrdered);
197200
}
198-
199201
renderer.EnsureLine();
200202
if (isOrdered)
201203
itemIndex++;
@@ -207,12 +209,22 @@ private static string GetContinuationIndent(string baseIndent, bool isOrdered) =
207209

208210
private static void RenderBlockWithIndentation(LlmMarkdownRenderer renderer, Block block, string baseIndent, bool isOrdered)
209211
{
212+
// Nested lists render in same context to preserve indentation
213+
if (block is ListBlock)
214+
{
215+
_ = renderer.Render(block);
216+
return;
217+
}
218+
219+
// Render other blocks in separate context and re-indent each line
210220
var blockOutput = DocumentationObjectPoolProvider.UseLlmMarkdownRenderer(renderer.BuildContext, block, static (tmpRenderer, obj) =>
211221
{
212222
_ = tmpRenderer.Render(obj);
213223
});
224+
214225
var continuationIndent = GetContinuationIndent(baseIndent, isOrdered);
215226
var lines = blockOutput.Split('\n');
227+
216228
for (var i = 0; i < lines.Length; i++)
217229
{
218230
var line = lines[i];
@@ -222,6 +234,7 @@ private static void RenderBlockWithIndentation(LlmMarkdownRenderer renderer, Blo
222234
{
223235
renderer.WriteLine();
224236
renderer.Write(continuationIndent);
237+
// Preserve exact code block formatting, trim other content
225238
renderer.Write(block is CodeBlock ? line : line.TrimStart());
226239
}
227240
else if (i < lines.Length - 1)
@@ -329,6 +342,7 @@ private static void RenderTableRowCells(LlmMarkdownRenderer renderer, TableRow r
329342
renderer.Writer.Write(" |");
330343
cellIndex++;
331344
}
345+
332346
renderer.WriteLine();
333347
}
334348

@@ -665,7 +679,6 @@ public class LlmDefinitionItemRenderer : MarkdownObjectRenderer<LlmMarkdownRende
665679
{
666680
protected override void Write(LlmMarkdownRenderer renderer, DefinitionItem obj)
667681
{
668-
669682
var first = obj.Cast<LeafBlock>().First();
670683
renderer.EnsureBlockSpacing();
671684
renderer.Write("<definition");
@@ -679,6 +692,7 @@ protected override void Write(LlmMarkdownRenderer renderer, DefinitionItem obj)
679692
var block = obj[index];
680693
LlmRenderingHelpers.RenderBlockWithIndentation(renderer, block);
681694
}
695+
682696
renderer.WriteLine("</definition>");
683697
}
684698

tests-integration/Elastic.Assembler.IntegrationTests/DocsSyncTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ public async Task TestApply()
306306
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.added" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 3);
307307
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.updated" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 1);
308308
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.deleted" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 1);
309-
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.total" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 5);
309+
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.skipped" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 1);
310+
tagObjects.Should().Contain(t => t.Key == "docs.sync.files.total" && Convert.ToInt64(t.Value, System.Globalization.CultureInfo.InvariantCulture) == 6);
310311
}
311312
}

tests/authoring/LlmMarkdown/LlmMarkdownOutput.fs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,9 @@ type ``directive in list should be indented correctly`` () =
327327
```python
328328
def hello():
329329
print("Hello, world!")
330-
```
330+
```
331331
- List item 2
332332
<tip>
333-
334333
- Nested list item 1
335334
- Nested list item 2
336335
</tip>
@@ -355,7 +354,6 @@ This is where the content for tab #2 goes.
355354
let ``rendered correctly`` () =
356355
markdown |> convertsToNewLLM """
357356
<tab-set>
358-
359357
<tab-item title="Tab #1 title">
360358
This is where the content for tab #1 goes.
361359
</tab-item>

0 commit comments

Comments
 (0)