From af9c7e015a3a3bae5896a05b82e08a83e3660c00 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 15 Jan 2025 16:50:01 +0100 Subject: [PATCH] Support unique callouts for enhanced code blocks (#209) --- .../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 b46b50b4..ae148f92 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 87b0fe46..b5b04c14 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 a7f2578a..d836d876 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 389bf5a3..8edde18d 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>