From 107f7b94404b8c9f34552fd93755539d4978c945 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 15:29:21 +0000 Subject: [PATCH 1/3] Add ConsoleMathInlineRenderer and ConsoleMathBlockRenderer Co-authored-by: boxofyellow <54955040+boxofyellow@users.noreply.github.com> --- .../data/example.md | 11 +++++ .../RendererTests.cs | 20 +++++++++ .../resources/bracketEscaping.md | 6 +++ .../resources/bracketEscaping.txt | 6 +++ .../resources/mathBlock.md | 3 ++ .../resources/mathBlock.txt | 7 ++++ .../resources/mathInline.md | 1 + .../resources/mathInline.txt | 3 ++ DisplayOptions.cs | 39 +++++++++++++++++ ObjectRenderers/ConsoleMathBlockRenderer.cs | 42 +++++++++++++++++++ ObjectRenderers/ConsoleObjectRenderers.cs | 10 +++++ ObjectRenderers/ConsoleRenderer.cs | 6 +++ docs/CHANGELOG.md | 18 ++++++++ 13 files changed, 172 insertions(+) create mode 100644 ConsoleMarkdownRenderer.Tests/resources/mathBlock.md create mode 100644 ConsoleMarkdownRenderer.Tests/resources/mathBlock.txt create mode 100644 ConsoleMarkdownRenderer.Tests/resources/mathInline.md create mode 100644 ConsoleMarkdownRenderer.Tests/resources/mathInline.txt create mode 100644 ObjectRenderers/ConsoleMathBlockRenderer.cs diff --git a/ConsoleMarkdownRenderer.Example/data/example.md b/ConsoleMarkdownRenderer.Example/data/example.md index ad542d7..715c1c0 100644 --- a/ConsoleMarkdownRenderer.Example/data/example.md +++ b/ConsoleMarkdownRenderer.Example/data/example.md @@ -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 @@ -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 \ No newline at end of file diff --git a/ConsoleMarkdownRenderer.Tests/RendererTests.cs b/ConsoleMarkdownRenderer.Tests/RendererTests.cs index b8479d3..c0fefe6 100644 --- a/ConsoleMarkdownRenderer.Tests/RendererTests.cs +++ b/ConsoleMarkdownRenderer.Tests/RendererTests.cs @@ -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() { @@ -876,8 +892,12 @@ private static Dictionary Counts(string text) Inserted = c_crazyFormat, Italic = c_crazyFormat, Marked = c_crazyFormat, + MathBlock = c_crazyFormat, + MathBlockLabel = c_crazyFormat, + MathInline = c_crazyFormat, QuotedBlock = c_crazyFormat, ShowFencedCodeBlockInfo = true, + ShowMathBlockLabel = true, Strikethrough = c_crazyFormat, Subscript = c_crazyFormat, Superscript = c_crazyFormat, diff --git a/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.md b/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.md index 0330cbb..5852340 100644 --- a/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.md +++ b/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.md @@ -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 +$$ diff --git a/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.txt b/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.txt index 080ff0c..60b10f5 100644 --- a/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.txt +++ b/ConsoleMarkdownRenderer.Tests/resources/bracketEscaping.txt @@ -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 │ │ +│ │ │ │ +│ └────────────────────────┘ │ │ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ │ │ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │ │ │ │ ┌───────────────────────────────────────────────────────┐ │ │ diff --git a/ConsoleMarkdownRenderer.Tests/resources/mathBlock.md b/ConsoleMarkdownRenderer.Tests/resources/mathBlock.md new file mode 100644 index 0000000..1e8c5b8 --- /dev/null +++ b/ConsoleMarkdownRenderer.Tests/resources/mathBlock.md @@ -0,0 +1,3 @@ +$$ +\int_0^1 x^2 dx = \frac{1}{3} +$$ diff --git a/ConsoleMarkdownRenderer.Tests/resources/mathBlock.txt b/ConsoleMarkdownRenderer.Tests/resources/mathBlock.txt new file mode 100644 index 0000000..e13172f --- /dev/null +++ b/ConsoleMarkdownRenderer.Tests/resources/mathBlock.txt @@ -0,0 +1,7 @@ +┌─────────────────────────────────────┐ +│ ┌─────────────────────────────────┐ │ +│ │ │ │ +│ │ \int_0^1 x^2 dx = \frac{1}{3} │ │ +│ │ │ │ +│ └─────────────────────────────────┘ │ +└─────────────────────────────────────┘ diff --git a/ConsoleMarkdownRenderer.Tests/resources/mathInline.md b/ConsoleMarkdownRenderer.Tests/resources/mathInline.md new file mode 100644 index 0000000..53af194 --- /dev/null +++ b/ConsoleMarkdownRenderer.Tests/resources/mathInline.md @@ -0,0 +1 @@ +An inline math expression $E = mc^2$ inside a paragraph. diff --git a/ConsoleMarkdownRenderer.Tests/resources/mathInline.txt b/ConsoleMarkdownRenderer.Tests/resources/mathInline.txt new file mode 100644 index 0000000..60be942 --- /dev/null +++ b/ConsoleMarkdownRenderer.Tests/resources/mathInline.txt @@ -0,0 +1,3 @@ +┌────────────────────────────────────────────────────────┐ +│ An inline math expression E = mc^2 inside a paragraph. │ +└────────────────────────────────────────────────────────┘ diff --git a/DisplayOptions.cs b/DisplayOptions.cs index a8ced68..9fa99c9 100644 --- a/DisplayOptions.cs +++ b/DisplayOptions.cs @@ -127,8 +127,42 @@ public sealed class DisplayOptions /// public TextStyle Marked { get; set; } = new(foreground: TextColor.Black, background: TextColor.Yellow); + /// + /// Style applied to the verbatim source of a + /// (display math delimited by $$ ... $$). Terminals cannot typeset LaTeX, so the raw + /// source is rendered with this style inside a fenced presentation similar to a code block. + /// + public TextStyle MathBlock { get; set; } = new(foreground: TextColor.Green, background: TextColor.Purple); + + /// + /// Style applied to the optional label emitted at the top of a + /// when is . + /// + public TextStyle MathBlockLabel { get; set; } = new(foreground: TextColor.Yellow, background: TextColor.Purple); + + /// + /// Text used for the optional label when + /// is . + /// + public string MathBlockLabelText { get; set; } = "math"; + + /// + /// Style applied to the verbatim source of a + /// (inline math delimited by $ ... $). Rendered with a code-like style so callers can + /// distinguish it visually from prose; defaults differ from so math is + /// also distinguishable from code. + /// + public TextStyle MathInline { get; set; } = new(foreground: TextColor.Green, background: TextColor.Purple); + public TextStyle QuotedBlock { get; set; } = new(decoration: TextDecoration.Italic); + /// + /// When set to , a label (see ) is emitted at the + /// top of each rendered , similar to how + /// emits the language identifier for a fenced code block. + /// + public bool ShowMathBlockLabel { get; set; } = false; + /// public TextStyle Strikethrough { get; set; } = new(decoration: TextDecoration.Strikethrough); @@ -196,9 +230,14 @@ 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, + ShowMathBlockLabel = this.ShowMathBlockLabel, Strikethrough = this.Strikethrough, Subscript = this.Subscript, Superscript = this.Superscript, diff --git a/ObjectRenderers/ConsoleMathBlockRenderer.cs b/ObjectRenderers/ConsoleMathBlockRenderer.cs new file mode 100644 index 0000000..3aee271 --- /dev/null +++ b/ObjectRenderers/ConsoleMathBlockRenderer.cs @@ -0,0 +1,42 @@ +using BoxOfYellow.ConsoleMarkdownRenderer.Styling; +using Markdig.Extensions.Mathematics; + +namespace BoxOfYellow.ConsoleMarkdownRenderer.ObjectRenderers +{ + internal class ConsoleMathBlockRenderer : ConsoleObjectRenderer + { + protected override void Write(ConsoleRenderer renderer, MathBlock obj) + { + renderer + .NewFrame() + .PushStyle(renderer.Options.MathBlock.ToSpectreStyle()) + .StartInline() + .AddInLine(Environment.NewLine); + + if (renderer.Options.ShowMathBlockLabel) + { + 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(); + } + } +} diff --git a/ObjectRenderers/ConsoleObjectRenderers.cs b/ObjectRenderers/ConsoleObjectRenderers.cs index 15b0f17..676a8cb 100644 --- a/ObjectRenderers/ConsoleObjectRenderers.cs +++ b/ObjectRenderers/ConsoleObjectRenderers.cs @@ -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; @@ -213,6 +214,15 @@ protected override void Write(ConsoleRenderer renderer, LiteralInline obj) => renderer.WriteEscape(ref obj.Content); } + internal class ConsoleMathInlineRenderer : ConsoleObjectRenderer + { + protected override void Write(ConsoleRenderer renderer, MathInline obj) + => renderer + .AddInLine($"[{renderer.Options.MathInline.ToSpectreStyle().ToMarkup()}]") + .WriteEscape(ref obj.Content) + .AddInLine("[/]"); + } + internal class ConsoleParagraphBlockRenderer : ConsoleObjectRenderer { protected override void Write(ConsoleRenderer renderer, ParagraphBlock obj) diff --git a/ObjectRenderers/ConsoleRenderer.cs b/ObjectRenderers/ConsoleRenderer.cs index 1b1e946..7d22c2a 100644 --- a/ObjectRenderers/ConsoleRenderer.cs +++ b/ObjectRenderers/ConsoleRenderer.cs @@ -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(), @@ -43,6 +48,7 @@ internal ConsoleRenderer(DisplayOptions options, bool omitAutolinkInlineRenderer new ConsoleListBlockRenderer(), new ConsoleListItemBlockRenderer(), new ConsoleLiteralInlineRenderer(), + new ConsoleMathInlineRenderer(), new ConsoleParagraphBlockRenderer(), new ConsoleQuoteBlockRenderer(), new ConsoleTableCellRenderer(), diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1045250..99ec8cf 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -49,6 +49,24 @@ - After Image - [#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 (previously silently dropped) using a distinctive code-like style; raw LaTeX source is preserved. + - ```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 + Image + - After + Image ### :wrench: Internal Improvements :wrench: - [#129](https://github.com/boxofyellow/ConsoleMarkdownRenderer/pull/129): Use ConfigureAwait(false) on awaits in published library code From f370c117cba65891e26322efb54ed8228c6eefe6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:32:12 +0000 Subject: [PATCH 2/3] Use MathBlockLabelText presence as the math-block label flag Co-authored-by: boxofyellow <54955040+boxofyellow@users.noreply.github.com> --- ConsoleMarkdownRenderer.Tests/RendererTests.cs | 2 +- DisplayOptions.cs | 17 +++++------------ ObjectRenderers/ConsoleMathBlockRenderer.cs | 2 +- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/ConsoleMarkdownRenderer.Tests/RendererTests.cs b/ConsoleMarkdownRenderer.Tests/RendererTests.cs index c0fefe6..855b335 100644 --- a/ConsoleMarkdownRenderer.Tests/RendererTests.cs +++ b/ConsoleMarkdownRenderer.Tests/RendererTests.cs @@ -894,10 +894,10 @@ private static Dictionary Counts(string text) Marked = c_crazyFormat, MathBlock = c_crazyFormat, MathBlockLabel = c_crazyFormat, + MathBlockLabelText = "math", MathInline = c_crazyFormat, QuotedBlock = c_crazyFormat, ShowFencedCodeBlockInfo = true, - ShowMathBlockLabel = true, Strikethrough = c_crazyFormat, Subscript = c_crazyFormat, Superscript = c_crazyFormat, diff --git a/DisplayOptions.cs b/DisplayOptions.cs index 9fa99c9..403bd6c 100644 --- a/DisplayOptions.cs +++ b/DisplayOptions.cs @@ -136,15 +136,16 @@ public sealed class DisplayOptions /// /// Style applied to the optional label emitted at the top of a - /// when is . + /// when is non-empty. /// public TextStyle MathBlockLabel { get; set; } = new(foreground: TextColor.Yellow, background: TextColor.Purple); /// - /// Text used for the optional label when - /// is . + /// Text used for the optional label, rendered at the top + /// of each math block similar to how emits the language identifier for + /// a fenced code block. When or empty, no label is emitted (the default). /// - public string MathBlockLabelText { get; set; } = "math"; + public string MathBlockLabelText { get; set; } = string.Empty; /// /// Style applied to the verbatim source of a @@ -156,13 +157,6 @@ public sealed class DisplayOptions public TextStyle QuotedBlock { get; set; } = new(decoration: TextDecoration.Italic); - /// - /// When set to , a label (see ) is emitted at the - /// top of each rendered , similar to how - /// emits the language identifier for a fenced code block. - /// - public bool ShowMathBlockLabel { get; set; } = false; - /// public TextStyle Strikethrough { get; set; } = new(decoration: TextDecoration.Strikethrough); @@ -237,7 +231,6 @@ public sealed class DisplayOptions QuotedBlock = this.QuotedBlock, ShowAbbreviationTitle = this.ShowAbbreviationTitle, ShowFencedCodeBlockInfo = this.ShowFencedCodeBlockInfo, - ShowMathBlockLabel = this.ShowMathBlockLabel, Strikethrough = this.Strikethrough, Subscript = this.Subscript, Superscript = this.Superscript, diff --git a/ObjectRenderers/ConsoleMathBlockRenderer.cs b/ObjectRenderers/ConsoleMathBlockRenderer.cs index 3aee271..eb822c9 100644 --- a/ObjectRenderers/ConsoleMathBlockRenderer.cs +++ b/ObjectRenderers/ConsoleMathBlockRenderer.cs @@ -13,7 +13,7 @@ protected override void Write(ConsoleRenderer renderer, MathBlock obj) .StartInline() .AddInLine(Environment.NewLine); - if (renderer.Options.ShowMathBlockLabel) + if (!string.IsNullOrEmpty(renderer.Options.MathBlockLabelText)) { renderer .AddInLine($"[{renderer.Options.MathBlockLabel.ToSpectreStyle().ToMarkup()}]") From b6d867dbca26ccec74e71a166d3f55c13957a4de Mon Sep 17 00:00:00 2001 From: boxofyellow <54955040+boxofyellow@users.noreply.github.com> Date: Sat, 16 May 2026 23:06:24 -0400 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: boxofyellow <54955040+boxofyellow@users.noreply.github.com> --- docs/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 99ec8cf..949e277 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -49,7 +49,7 @@ - After Image - [#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 (previously silently dropped) using a distinctive code-like style; raw LaTeX source is preserved. +- [#145](https://github.com/boxofyellow/ConsoleMarkdownRenderer/pull/145): Render Markdig MathInline and MathBlock nodes - ```markdown Inline math $E = mc^2$ and block math: @@ -64,9 +64,9 @@ \int_0^1 x^2 dx = \frac{1}{3} $$ - Before - Image + Image - After - Image + Image ### :wrench: Internal Improvements :wrench: - [#129](https://github.com/boxofyellow/ConsoleMarkdownRenderer/pull/129): Use ConfigureAwait(false) on awaits in published library code