Skip to content

Commit f90b85e

Browse files
Copilotleemthompoclaude
authored
Render inline Markdown in automatic callouts and update docs (#3324)
* Initial plan * fix: render markdown in magic callouts Agent-Logs-Url: https://github.com/elastic/docs-builder/sessions/d3957e8a-7eab-4572-9244-4e24fa63a74e Co-authored-by: leemthompo <32779855+leemthompo@users.noreply.github.com> * test: align magic callout regression naming Agent-Logs-Url: https://github.com/elastic/docs-builder/sessions/d3957e8a-7eab-4572-9244-4e24fa63a74e Co-authored-by: leemthompo <32779855+leemthompo@users.noreply.github.com> * fix: import markdig for magic callout rendering Agent-Logs-Url: https://github.com/elastic/docs-builder/sessions/00950126-9a51-427f-9b93-9d838ae7df13 Co-authored-by: leemthompo <32779855+leemthompo@users.noreply.github.com> * test: fix magic callout link regression fixture Agent-Logs-Url: https://github.com/elastic/docs-builder/sessions/3df80900-27a1-4d02-b422-7c98e5832c2d Co-authored-by: leemthompo <32779855+leemthompo@users.noreply.github.com> * docs: update code callouts section for clarity and inline formatting Reorder automatic callouts before explicit, add decision guidance, and document inline Markdown support in callout text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: don't make people read raw regex Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: expand callout examples and note link limitation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: fold admonitions into intro prose per review Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: leemthompo <32779855+leemthompo@users.noreply.github.com> Co-authored-by: Liam Thompson <leemthompo@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 54e0d1d commit f90b85e

4 files changed

Lines changed: 127 additions & 64 deletions

File tree

docs/syntax/code.md

Lines changed: 64 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,72 @@ project:
3535
3636
### Code callouts
3737
38-
There are two ways to add callouts to a code block. When using callouts, you must use one callout format. You cannot combine explicit and magic callouts.
38+
There are two ways to add callouts to a code block: **automatic** and **explicit**. Automatic callouts are best for single-line descriptions. Numbering is handled for you, so adding or removing a callout does not require renumbering. Each description sits next to the code it annotates, making the source easier to read. Use explicit callouts when a description needs multiple lines, block-level content, or links.
3939
40-
#### Explicit callouts
40+
You cannot combine both formats in the same code block. `docs-builder` will throw an error if both are present.
41+
42+
#### Automatic callouts
43+
44+
If a code block contains code comments in the form of `//` or `#`, callouts are automatically created. The comment must follow code on the same line. A comment on its own line is not treated as a callout.
45+
46+
Callout text supports inline Markdown formatting such as `` `code` ``, **bold**, and *italic*. Automatic callouts do not currently support links.
47+
48+
::::{tab-set}
49+
50+
:::{tab-item} Output
51+
52+
```python
53+
api_key = ApiKey("<API_KEY>") # Set up the `api` key
54+
client = Elasticsearch("<CLOUD_ID>", api_key) # Connect to your **Elastic Cloud** deployment
55+
response = client.search(index="my-index") # Returns a *list* of matching documents
56+
```
57+
58+
:::
4159

42-
Add `<\d+>` to the end of a line to explicitly create a code callout.
60+
:::{tab-item} Markdown
4361

44-
An ordered list with the same number of items as callouts must follow the code block. If the number of list items doesn’t match the callouts, docs-builder will throw an error.
62+
````markdown
63+
```python
64+
api_key = ApiKey("<API_KEY>") # Set up the `api` key
65+
client = Elasticsearch("<CLOUD_ID>", api_key) # Connect to your **Elastic Cloud** deployment
66+
response = client.search(index="my-index") # Returns a *list* of matching documents
67+
```
68+
````
69+
:::
70+
71+
::::
4572

73+
Comments on their own line are left as-is:
74+
75+
::::{tab-set}
76+
77+
:::{tab-item} Output
78+
79+
```csharp
80+
// THIS IS NOT A CALLOUT
81+
var apiKey = new ApiKey("<API_KEY>"); // This is a callout
82+
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
83+
```
84+
85+
:::
86+
87+
:::{tab-item} Markdown
88+
89+
````markdown
90+
```csharp
91+
// THIS IS NOT A CALLOUT
92+
var apiKey = new ApiKey("<API_KEY>"); // This is a callout
93+
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
94+
```
95+
````
96+
97+
:::
98+
99+
::::
100+
101+
#### Explicit callouts
102+
103+
Add a numbered marker like `<1>`, `<2>`, etc. to the end of a line to create a callout. An ordered list with the same number of items must follow the code block. If the counts don’t match, docs-builder throws an error.
46104

47105
::::{tab-set}
48106

@@ -74,7 +132,7 @@ project:
74132
75133
::::
76134
77-
You can also have one block element in between the code block and the callout list:
135+
You can have one block element between the code block and the callout list:
78136
79137
::::{tab-set}
80138
@@ -135,64 +193,7 @@ render(input2);
135193

136194
::::
137195

138-
#### Automatic callouts
139-
140-
If a code block contains code comments in the form of `//` or `#`, callouts are automatically created.
141-
142-
143-
::::{tab-set}
144-
145-
:::{tab-item} Output
146-
147-
```csharp
148-
var apiKey = new ApiKey("<API_KEY>"); // Set up the api key
149-
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
150-
```
151-
152-
:::
153-
154-
:::{tab-item} Markdown
155-
156-
````markdown
157-
```csharp
158-
var apiKey = new ApiKey("<API_KEY>"); // Set up the api key
159-
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
160-
```
161-
````
162-
:::
163-
164-
165-
::::
166-
167-
Code comments must follow code to be hoisted as a callout. For example:
168-
169-
::::{tab-set}
170-
171-
:::{tab-item} Output
172-
173-
```csharp
174-
// THIS IS NOT A CALLOUT
175-
var apiKey = new ApiKey("<API_KEY>"); // This is a callout
176-
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
177-
```
178-
179-
:::
180-
181-
:::{tab-item} Markdown
182-
183-
````markdown
184-
```csharp
185-
// THIS IS NOT A CALLOUT
186-
var apiKey = new ApiKey("<API_KEY>"); // This is a callout
187-
var client = new ElasticsearchClient("<CLOUD_ID>", apiKey);
188-
```
189-
````
190-
191-
:::
192-
193-
::::
194-
195-
#### Align callouts
196+
##### Align callouts
196197

197198
You can align callouts with spaces.
198199

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class EnhancedCodeBlock(BlockParser parser, ParserContext context)
2323
{
2424
public BuildContext Build { get; } = context.Build;
2525

26+
public ParserContext Context { get; } = context;
27+
2628
public IFileInfo CurrentFile { get; } = context.MarkdownSourcePath;
2729

2830
public bool SkipValidation { get; } = context.SkipValidation;

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
using Elastic.Markdown.Myst.Comments;
1010
using Elastic.Markdown.Myst.Directives.AppliesTo;
1111
using Elastic.Markdown.Myst.Directives.Contributors;
12+
using Markdig;
1213
using Markdig.Helpers;
1314
using Markdig.Renderers;
1415
using Markdig.Renderers.Html;
1516
using Markdig.Syntax;
17+
using Microsoft.AspNetCore.Html;
1618
using RazorSlices;
1719

1820
namespace Elastic.Markdown.Myst.CodeBlocks;
@@ -238,14 +240,48 @@ protected override void Write(HtmlRenderer renderer, EnhancedCodeBlock block)
238240
foreach (var c in block.UniqueCallOuts)
239241
{
240242
_ = renderer.WriteLine("<li>");
241-
_ = renderer.WriteLine(c.Text);
243+
_ = renderer.WriteLine(RenderCalloutMarkdown(block, c).Value ?? string.Empty);
242244
_ = renderer.WriteLine("</li>");
243245
}
244246

245247
_ = renderer.WriteLine("</ol>");
246248
}
247249
}
248250

251+
private static HtmlString RenderCalloutMarkdown(EnhancedCodeBlock block, CallOut callOut)
252+
{
253+
if (string.IsNullOrWhiteSpace(callOut.Text))
254+
return HtmlString.Empty;
255+
256+
var document = MarkdownParser.ParseMarkdownStringAsync(
257+
block.Build,
258+
block.Context,
259+
callOut.Text,
260+
block.CurrentFile,
261+
block.Context.YamlFrontMatter,
262+
MarkdownParser.Pipeline);
263+
264+
if (document.Count == 1 && document.FirstOrDefault() is ParagraphBlock paragraph && paragraph.Inline != null)
265+
return RenderInlineMarkdown(paragraph);
266+
267+
var html = document.ToHtml(MarkdownParser.Pipeline);
268+
return new HtmlString(html.EnsureTrimmed());
269+
}
270+
271+
private static HtmlString RenderInlineMarkdown(ParagraphBlock paragraph)
272+
{
273+
if (paragraph.Inline is null)
274+
return HtmlString.Empty;
275+
276+
var subscription = DocumentationObjectPoolProvider.HtmlRendererPool.Get();
277+
subscription.HtmlRenderer.WriteChildren(paragraph.Inline);
278+
279+
var result = subscription.RentedStringBuilder?.ToString();
280+
DocumentationObjectPoolProvider.HtmlRendererPool.Return(subscription);
281+
282+
return result == null ? HtmlString.Empty : new HtmlString(result.EnsureTrimmed());
283+
}
284+
249285
[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly")]
250286
private static void RenderAppliesToHtml(HtmlRenderer renderer, AppliesToDirective appliesToDirective)
251287
{

tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.IO.Abstractions.TestingHelpers;
56
using AwesomeAssertions;
67
using Elastic.Markdown.Myst.CodeBlocks;
78
using Elastic.Markdown.Tests.Inline;
@@ -51,6 +52,29 @@ public void ParsesMagicCallOuts() => Block!.CallOuts
5152
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
5253
}
5354

55+
public class MagicCallOutWithFormatting(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp",
56+
"""
57+
var x = 1; // this uses `formatting` and a [link](testing/req.md)
58+
"""
59+
)
60+
{
61+
protected override void AddToFileSystem(MockFileSystem fileSystem) =>
62+
fileSystem.AddFile("docs/testing/req.md", new MockFileData("# Requirements"));
63+
64+
[Fact]
65+
public void RendersFormattedInlineMarkdown() =>
66+
Html.ShouldContainHtml(
67+
"""
68+
<ol class="code-callouts">
69+
<li>this uses <code>formatting</code> and a <a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">link</a></li>
70+
</ol>
71+
"""
72+
);
73+
74+
[Fact]
75+
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
76+
}
77+
5478
public class ClassicCallOutsRequiresContent(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp",
5579
"""
5680
var x = 1; <1>

0 commit comments

Comments
 (0)