OdeToCode IC Logo

A Custom Renderer Extension for Markdig

Thursday, January 23, 2020

A couple years ago I decided to stop using Windows Live Writer for authoring blog posts and build my own publishing tools using markdown and VSCode. Live Writer was a fantastic tool during its heyday, but some features started to feel cumbersome. Adding code into a blog post, as one example.

This blog uses SyntaxHighlighter to render code blocks, which requires HTML in a specific format. With WLW the HTML formatting required a toggle into HTML mode, or using an extension which was no longer supported in the OSS version of WLW.

What I really wanted was to author a post in markdown and use simple code fences to place code into a post.

``` csharp
public void AnOdeToCode()
{

}
```

Simple!

All I'd need is a markdown processor that would allow me to add some custom rendering for code fences.

Markdig Extensions

Markdig is a fast, powerful, CommonMark compliant, extensible Markdown processor for .NET. Thanks to Rick Strahl for bringing the library to my attention. I use Markdig in my tools to transform a markdown file into HTML for posting here on the site.

There are at least a couple different techniques you can use to write an extension for Markdig. What I needed was an extension point to render SyntaxHighlighter flavored HTML for every code fence in a post. With Markdig, this means adding an HtmlOBjectRenderer into the processing pipeline.

public class PreCodeRenderer : HtmlObjectRenderer<CodeBlock>
{
    private CodeBlockRenderer originalCodeBlockRenderer;
    
    public PreCodeRenderer(CodeBlockRenderer originalCodeBlockRenderer = null)
    {
        this.originalCodeBlockRenderer = originalCodeBlockRenderer ?? new CodeBlockRenderer();
    }
    public bool OutputAttributesOnPre { get; set; }
    protected override void Write(HtmlRenderer renderer, CodeBlock obj)
    {
        renderer.EnsureLine();
    
        var fencedCodeBlock = obj as FencedCodeBlock;
        if (fencedCodeBlock?.Info != null)
        {
            renderer.Write($"<pre class=\"brush: {fencedCodeBlock.Info}; gutter: false; toolbar: false; \">");
            renderer.EnsureLine();
            renderer.WriteLeafRawLines(obj, true, true);
            renderer.WriteLine("</pre>");
        }
        else
        {
            originalCodeBlockRenderer.Write(renderer, obj);
        }                
    }
}

Note that the Info property of a FencedCodeBlock will contain the info string, which is commonly used to specify the language of the code (csharp, xml, javascript, plain, go). The renderer builds a pre tag that SyntaxHighlighter will know how to use. The last step, the easy step, is to add PerCodeRenderer into a MarkdownPipelineBuilder before telling Markdig to process your markdown.