Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ConsoleMarkdownRenderer.Example/data/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,16 @@ Inside code spans (`:smile:`) and code blocks, the original text is preserved:

Some text after the block

## Mathematics (LaTeX source rendered verbatim)

Markdig parses inline math like $E = mc^2$ and block math like

$$
\int_0^1 x^2 dx = \frac{1}{3}
$$

into AST nodes. Terminals cannot typeset LaTeX, so the raw source is rendered with a distinctive style.

### TODOs

- [x] Code Blocks
Expand All @@ -217,6 +227,7 @@ Some text after the block
- [x] Custom containers (admonitions)
- [x] Abbreviations
- [x] Figures
- [x] Mathematics (inline `$...$` and block `$$...$$` LaTeX source)
- [ ] One to always leave unchecked

And here is the end
20 changes: 20 additions & 0 deletions ConsoleMarkdownRenderer.Tests/RendererTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,22 @@ public void RendererTests_CodeInlineTest(bool useCrazy)
AssertMarkdownYieldsFormat("codeInline", "in line code", new Style(foreground: Color.Yellow, background: Color.Blue), useCrazy);
}

[TestMethod]
[DataRow(false)]
[DataRow(true)]
public void RendererTests_MathInlineTest(bool useCrazy)
{
AssertMarkdownYieldsFormat("mathInline", "E = mc^2", new Style(foreground: Color.Green, background: Color.Purple), useCrazy);
}

[TestMethod]
[DataRow(false)]
[DataRow(true)]
public void RendererTests_MathBlockTest(bool useCrazy)
{
AssertMarkdownYieldsFormat("mathBlock", "\\int_0^1 x^2 dx = \\frac{1}{3}", new Style(foreground: Color.Green, background: Color.Purple), useCrazy);
}

[TestMethod]
public void RendererTests_FencedCodeBlockInfoDisabledByDefault()
{
Expand Down Expand Up @@ -876,6 +892,10 @@ private static Dictionary<string, int> Counts(string text)
Inserted = c_crazyFormat,
Italic = c_crazyFormat,
Marked = c_crazyFormat,
MathBlock = c_crazyFormat,
MathBlockLabel = c_crazyFormat,
MathBlockLabelText = "math",
MathInline = c_crazyFormat,
QuotedBlock = c_crazyFormat,
ShowFencedCodeBlockInfo = true,
Strikethrough = c_crazyFormat,
Expand Down
6 changes: 6 additions & 0 deletions ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ Paragraph mentioning testAbbr abbreviation usage.
^^^ [test30] figure caption with **[test31] bold** content.
![[test32] alt](http://example.com/img.png)
^^^

Inline math $[test33] \frac{1}{2}$ embedded.

$$
[test34] \int_0^1 dx
$$
6 changes: 6 additions & 0 deletions ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
│ │ [test30] figure caption with [test31] bold content. │ │
│ │ ![[test32] alt](http://example.com/img.png) │ │
│ └─────────────────────────────────────────────────────┘ │
│ Inline math [test33] \frac{1}{2} embedded. │
│ ┌────────────────────────┐ │
│ │ │ │
│ │ [test34] \int_0^1 dx │ │
│ │ │ │
│ └────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │ │
│ │ ┌───────────────────────────────────────────────────────┐ │ │
Expand Down
3 changes: 3 additions & 0 deletions ConsoleMarkdownRenderer.Tests/resources/mathBlock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$$
\int_0^1 x^2 dx = \frac{1}{3}
$$
7 changes: 7 additions & 0 deletions ConsoleMarkdownRenderer.Tests/resources/mathBlock.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
┌─────────────────────────────────────┐
│ ┌─────────────────────────────────┐ │
│ │ │ │
│ │ \int_0^1 x^2 dx = \frac{1}{3} │ │
│ │ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
1 change: 1 addition & 0 deletions ConsoleMarkdownRenderer.Tests/resources/mathInline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
An inline math expression $E = mc^2$ inside a paragraph.
3 changes: 3 additions & 0 deletions ConsoleMarkdownRenderer.Tests/resources/mathInline.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
┌────────────────────────────────────────────────────────┐
│ An inline math expression E = mc^2 inside a paragraph. │
└────────────────────────────────────────────────────────┘
32 changes: 32 additions & 0 deletions DisplayOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,34 @@ public sealed class DisplayOptions
/// <see cref="Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Marked"/>
public TextStyle Marked { get; set; } = new(foreground: TextColor.Black, background: TextColor.Yellow);

/// <summary>
/// Style applied to the verbatim source of a <see cref="Markdig.Extensions.Mathematics.MathBlock"/>
/// (display math delimited by <c>$$ ... $$</c>). Terminals cannot typeset LaTeX, so the raw
/// source is rendered with this style inside a fenced presentation similar to a code block.
/// </summary>
public TextStyle MathBlock { get; set; } = new(foreground: TextColor.Green, background: TextColor.Purple);

/// <summary>
/// Style applied to the optional label emitted at the top of a <see cref="Markdig.Extensions.Mathematics.MathBlock"/>
/// when <see cref="MathBlockLabelText"/> is non-empty.
/// </summary>
public TextStyle MathBlockLabel { get; set; } = new(foreground: TextColor.Yellow, background: TextColor.Purple);

/// <summary>
/// Text used for the optional <see cref="Markdig.Extensions.Mathematics.MathBlock"/> label, rendered at the top
/// of each math block similar to how <see cref="ShowFencedCodeBlockInfo"/> emits the language identifier for
/// a fenced code block. When <see langword="null"/> or empty, no label is emitted (the default).
/// </summary>
public string MathBlockLabelText { get; set; } = string.Empty;

/// <summary>
/// Style applied to the verbatim source of a <see cref="Markdig.Extensions.Mathematics.MathInline"/>
/// (inline math delimited by <c>$ ... $</c>). Rendered with a code-like style so callers can
/// distinguish it visually from prose; defaults differ from <see cref="CodeInLine"/> so math is
/// also distinguishable from code.
/// </summary>
public TextStyle MathInline { get; set; } = new(foreground: TextColor.Green, background: TextColor.Purple);

public TextStyle QuotedBlock { get; set; } = new(decoration: TextDecoration.Italic);

/// <see cref="Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Strikethrough"/>
Expand Down Expand Up @@ -196,6 +224,10 @@ public sealed class DisplayOptions
Inserted = this.Inserted,
Italic = this.Italic,
Marked = this.Marked,
MathBlock = this.MathBlock,
MathBlockLabel = this.MathBlockLabel,
MathBlockLabelText = this.MathBlockLabelText,
MathInline = this.MathInline,
QuotedBlock = this.QuotedBlock,
ShowAbbreviationTitle = this.ShowAbbreviationTitle,
ShowFencedCodeBlockInfo = this.ShowFencedCodeBlockInfo,
Expand Down
42 changes: 42 additions & 0 deletions ObjectRenderers/ConsoleMathBlockRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using BoxOfYellow.ConsoleMarkdownRenderer.Styling;
using Markdig.Extensions.Mathematics;

namespace BoxOfYellow.ConsoleMarkdownRenderer.ObjectRenderers
{
internal class ConsoleMathBlockRenderer : ConsoleObjectRenderer<MathBlock>
{
protected override void Write(ConsoleRenderer renderer, MathBlock obj)
{
renderer
.NewFrame()
.PushStyle(renderer.Options.MathBlock.ToSpectreStyle())
.StartInline()
.AddInLine(Environment.NewLine);

if (!string.IsNullOrEmpty(renderer.Options.MathBlockLabelText))
{
renderer
.AddInLine($"[{renderer.Options.MathBlockLabel.ToSpectreStyle().ToMarkup()}]")
.WriteEscape($" [{renderer.Options.MathBlockLabelText}]")
.AddInLine("[/]")
.AddInLine(Environment.NewLine);
}

for (int i = 0; i < obj.Lines.Lines.Length; i++)
{
if (!string.IsNullOrEmpty(obj.Lines.Lines[i].Slice.Text))
{
renderer
.AddInLine(" ")
.WriteEscape(ref obj.Lines.Lines[i].Slice)
.AddInLine(Environment.NewLine);
}
}

renderer
.EndInline()
.PopStyle()
.CompleteFrame();
}
}
}
10 changes: 10 additions & 0 deletions ObjectRenderers/ConsoleObjectRenderers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Markdig.Extensions.DefinitionLists;
using Markdig.Extensions.Figures;
using Markdig.Extensions.Footnotes;
using Markdig.Extensions.Mathematics;
using Markdig.Extensions.TaskLists;
using Markdig.Renderers;
using Markdig.Syntax;
Expand Down Expand Up @@ -213,6 +214,15 @@ protected override void Write(ConsoleRenderer renderer, LiteralInline obj)
=> renderer.WriteEscape(ref obj.Content);
}

internal class ConsoleMathInlineRenderer : ConsoleObjectRenderer<MathInline>
{
protected override void Write(ConsoleRenderer renderer, MathInline obj)
=> renderer
.AddInLine($"[{renderer.Options.MathInline.ToSpectreStyle().ToMarkup()}]")
.WriteEscape(ref obj.Content)
.AddInLine("[/]");
}

internal class ConsoleParagraphBlockRenderer : ConsoleObjectRenderer<ParagraphBlock>
{
protected override void Write(ConsoleRenderer renderer, ParagraphBlock obj)
Expand Down
6 changes: 6 additions & 0 deletions ObjectRenderers/ConsoleRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ internal ConsoleRenderer(DisplayOptions options, bool omitAutolinkInlineRenderer

ObjectRenderers.AddRange([
new ConsoleAbbreviationInlineRenderer(),
// ConsoleMathBlockRenderer must precede ConsoleCodeBlockRenderer because
// Markdig's MathBlock extends FencedCodeBlock (which extends CodeBlock),
// and renderer dispatch uses type assignability — so the math-specific
// renderer has to win before the code-block renderer claims the type.
new ConsoleMathBlockRenderer(),
new ConsoleCodeBlockRenderer(),
new ConsoleCodeInlineRenderer(),
new ConsoleCustomContainerInlineRenderer(),
Expand All @@ -43,6 +48,7 @@ internal ConsoleRenderer(DisplayOptions options, bool omitAutolinkInlineRenderer
new ConsoleListBlockRenderer(),
new ConsoleListItemBlockRenderer(),
new ConsoleLiteralInlineRenderer(),
new ConsoleMathInlineRenderer(),
new ConsoleParagraphBlockRenderer(),
new ConsoleQuoteBlockRenderer(),
new ConsoleTableCellRenderer(),
Expand Down
18 changes: 18 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@
- After
<img width="293" height="66" alt="Image" src="https://github.com/user-attachments/assets/0d44e717-9397-4bbf-8a95-2ab942e25140" />
- [#144](https://github.com/boxofyellow/ConsoleMarkdownRenderer): Expose Spectre.Console Rule style for thematic breaks
- [#145](https://github.com/boxofyellow/ConsoleMarkdownRenderer/pull/145): Render Markdig MathInline and MathBlock nodes
- ```markdown
Inline math $E = mc^2$ and block math:

$$
\int_0^1 x^2 dx = \frac{1}{3}
$$
```
- Rendered
Inline math $E = mc^2$ and block math:

$$
\int_0^1 x^2 dx = \frac{1}{3}
$$
- Before
<img width="264" height="60" alt="Image" src="https://github.com/user-attachments/assets/223966a1-956a-42c2-acb2-17f23d682484" />
- After
<img width="264" height="60" alt="Image" src="https://github.com/user-attachments/assets/c3be7ff6-0267-4c2a-98f0-48b6b913ef38" />

### :wrench: Internal Improvements :wrench:
- [#129](https://github.com/boxofyellow/ConsoleMarkdownRenderer/pull/129): Use ConfigureAwait(false) on awaits in published library code
Expand Down
Loading