This site provides a machine-readable index at /llms.txt.

Skip to main content Skip to navigation

Add a custom syntax highlighter

Use this approach for fences tagged with a language token — a DSL, config format, or domain notation — that TextMateSharp does not cover, when styled output is the goal but authoring a full TextMate grammar is not. For line-level callouts on an already-supported language, see Highlight, diff, focus, or flag lines inside a code block. When the goal is transforming fence bodies rather than colouring tokens, see Add a custom fence syntax.

Assumptions

  • An existing Pennington site rendering markdown fences (see the Getting Started tutorial if not).
  • A target language not already served by TextMateHighlighter (priority 50) or ShellHighlighter (priority 75) — confirm by rendering a fence and inspecting the emitted HTML for the built-in token spans.
  • Comfort producing HTML for a fence body by hand — ICodeHighlighter.Highlight returns a raw HTML string, so the implementation owns escaping and the outer <pre><code> wrapper.

For a working setup, see examples/ExtensibilityLabExamplePipelineHighlighter.cs stakes out a fictional pipeline DSL and Program.cs registers it against a bare AddPennington host.


Steps

  1. 1

    Implement ICodeHighlighter

    The contract requires three members: SupportedLanguages, Priority, and Highlight(code, language). The next three steps fence each of those members separately from the example PipelineHighlighter at examples/ExtensibilityLabExample/PipelineHighlighter.cs, which wraps keywords, arrows, and string literals in classed <span> elements and HTML-encodes everything else.

  2. 2

    Declare SupportedLanguages

    Every language token returned here maps to a fence language (for example, ```pipeline) that routes to the highlighter. Use StringComparer.OrdinalIgnoreCase to match Pipeline and PIPELINE as well.

    /// <summary>The languages this highlighter claims.</summary>
    public IReadOnlySet<string> SupportedLanguages { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        { "pipeline" };
  3. 3

    Set Priority

    Higher priority wins when multiple highlighters claim the same language. The built-in chain places PlainTextHighlighter at 0, TextMateHighlighter at 50, and ShellHighlighter at 75. The example uses 100 so the pipeline fence routes here even if a future TextMate grammar also claims it, while leaving room below for any secondary fallbacks that ship alongside.

    /// <summary>Priority for highlighter dispatch — higher wins.</summary>
    public int Priority => 100;
  4. 4

    Produce the fence HTML in Highlight

    Highlight receives the raw fence body and the language token and returns the full HTML for the block, including the outer <pre><code> wrapper — the same convention the built-in highlighters follow. HTML-encode every character not explicitly wrapped in a span; the pipeline example uses WebUtility.HtmlEncode on every literal path to prevent injection. Full implementation: examples/ExtensibilityLabExample/PipelineHighlighter.cs.

  5. 5

    Register with HighlightingOptions.AddHighlighter

    PenningtonOptions.Highlighting exposes an AddHighlighter overload that inserts the instance into the priority-sorted chain resolved by HighlightingService. Call it inside the AddPennington delegate so the highlighter is active for both dotnet run and dotnet run -- build output.

    _highlighters.Add(highlighter)
  6. 6

    Author a fence that targets your language

    Any markdown fence tagged with one of the strings from SupportedLanguages now routes to the custom highlighter instead of the fallback chain.

    ```pipeline
    source "orders" -> filter where=paid | transform total=sum | sink "warehouse"

Verify

  • Run dotnet run --project examples/ExtensibilityLabExample and visit /pipeline-demo/.
  • Expect each keyword, arrow, and string literal inside the pipeline fence to carry a pipeline-* CSS class; the neighbouring text fence should render with no spans (fallback PlainTextHighlighter).
  • Static build: dotnet run --project examples/ExtensibilityLabExample -- build output — grep the emitted HTML for class="pipeline-keyword" to confirm the highlighter also runs during publish.