From d9157f84d19ecab2f82f9b93f28681fa63532580 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Jan 2025 13:41:05 +0100 Subject: [PATCH] Support unique callouts for enhanced code blocks Updated logic to handle unique callouts by index and ensure proper rendering in HTML. Adjusted tests to validate the new behavior and improved error handling for mismatched annotations and lists. This now also support reusing callouts e.g ````markdown ```js var x = 1; <1> var y = x - 2; <2> var z = y - 2; <2> ``` 1. a 2. b ```` --- .../Myst/CodeBlocks/EnhancedCodeBlock.cs | 2 ++ .../EnhancedCodeBlockHtmlRenderer.cs | 6 ++-- .../CodeBlocks/EnhancedCodeBlockParser.cs | 7 ++++- .../CodeBlocks/CallOutTests.cs | 29 +++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlock.cs b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlock.cs index b46b50b44..ae148f92f 100644 --- a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlock.cs +++ b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlock.cs @@ -22,6 +22,8 @@ public class EnhancedCodeBlock(BlockParser parser, ParserContext context) public List? CallOuts { get; set; } + public IReadOnlyCollection UniqueCallOuts => CallOuts?.DistinctBy(c => c.Index).ToList() ?? []; + public bool InlineAnnotations { get; set; } public string Language { get; set; } = "unknown"; diff --git a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs index 87b0fe463..b5b04c14c 100644 --- a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs +++ b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockHtmlRenderer.cs @@ -25,7 +25,7 @@ private static void RenderRazorSlice(RazorSlice slice, HtmlRenderer render } protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block) { - var callOuts = block.CallOuts ?? []; + var callOuts = block.UniqueCallOuts; var slice = Code.Create(new CodeViewModel { @@ -46,7 +46,7 @@ protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block) var siblingBlock = block.Parent[index + 1]; if (siblingBlock is not ListBlock) block.EmitError("Code block with annotations is not followed by a list"); - if (siblingBlock is ListBlock l && l.Count != callOuts.Count) + if (siblingBlock is ListBlock l && l.Count < callOuts.Count) { block.EmitError( $"Code block has {callOuts.Count} callouts but the following list only has {l.Count}"); @@ -84,7 +84,7 @@ protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block) else if (block.InlineAnnotations) { renderer.WriteLine("
    "); - foreach (var c in block.CallOuts ?? []) + foreach (var c in block.UniqueCallOuts) { renderer.WriteLine("
  1. "); renderer.WriteLine(c.Text); diff --git a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs index a7f2578a3..d836d8763 100644 --- a/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs +++ b/src/Elastic.Markdown/Myst/CodeBlocks/EnhancedCodeBlockParser.cs @@ -172,9 +172,14 @@ public override bool Close(BlockProcessor processor, Block block) callOutIndex++; var callout = span.Slice(match.Index + startIndex, match.Length - startIndex); + var index = callOutIndex; + if (!inlineCodeAnnotation && int.TryParse(callout.Trim(['<', '>']), out index)) + { + + } return new CallOut { - Index = callOutIndex, + Index = index, Text = callout.TrimStart('/').TrimStart('#').TrimStart().ToString(), InlineCodeAnnotation = inlineCodeAnnotation, SliceStart = startIndex, diff --git a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs index 389bf5a38..8edde18d0 100644 --- a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs +++ b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs @@ -117,6 +117,35 @@ public void RequiresContentToFollow() => Collector.Diagnostics.Should().HaveCoun .And.OnlyContain(c => c.Message.StartsWith("Code block has 2 callouts but the following list only has 1")); } +public class ClassicCallOutsReuseHighlights(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp", +""" +var x = 1; <1> +var y = x - 2; <2> +var z = y - 2; <2> +""", +""" +1. The first +2. The second appears twice +""" + + ) +{ + [Fact] + public void SeesTwoUniqueCallouts() => Block!.UniqueCallOuts + .Should().NotBeNullOrEmpty() + .And.HaveCount(2) + .And.OnlyContain(c => c.Text.StartsWith("<")); + + [Fact] + public void ParsesAllForLineInformation() => Block!.CallOuts + .Should().NotBeNullOrEmpty() + .And.HaveCount(3) + .And.OnlyContain(c => c.Text.StartsWith("<")); + + [Fact] + public void RequiresContentToFollow() => Collector.Diagnostics.Should().BeEmpty(); +} + public class ClassicCallOutWithTheRightListItems(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp", """ var x = 1; <1>