package parser import ( "bytes" "fmt" "github.com/alecthomas/chroma/v2" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" "github.com/yuin/goldmark" "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/util" ) // mermaidLang is the fenced-code-block language tag that triggers Mermaid // rendering. var mermaidLang = []byte("mermaid") // chromaFormatter renders tokenized code as HTML using CSS classes (rather // than inline styles), so the actual colors live in web/assets/chroma.css // (see web/gen_chroma_css.go) and can vary between light and dark mode. var chromaFormatter = chromahtml.New(chromahtml.WithClasses(true)) // chromaBaseStyle is passed to chromaFormatter.Format as required by its // signature. In WithClasses mode its colors are unused (they live in the // generated stylesheet instead); only its background-vs-foreground // classification matters, which is the same across chroma styles. var chromaBaseStyle = styles.Get("github") // md is the shared goldmark instance used to convert slide Markdown bodies // to HTML. It enables GFM (tables, strikethrough, autolinks, task lists), // footnotes, raw inline/block HTML passthrough, and Mermaid code-block // rendering. var md = goldmark.New( goldmark.WithExtensions(extension.GFM, extension.Footnote, mermaidExtension), goldmark.WithRendererOptions(html.WithUnsafe()), ) // renderMarkdown converts a Markdown slide body to HTML. func renderMarkdown(body []byte) ([]byte, error) { var buf bytes.Buffer if err := md.Convert(body, &buf); err != nil { return nil, err } return buf.Bytes(), nil } // mermaidExtender registers a NodeRenderer that intercepts ```mermaid fenced // code blocks so they render as
...instead of // the default
..., since
// mermaid.js expects the diagram source as the direct text content of a
// element.
type mermaidExtender struct{}
var mermaidExtension goldmark.Extender = mermaidExtender{}
func (mermaidExtender) Extend(m goldmark.Markdown) {
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(&mermaidRenderer{}, 100),
))
}
// mermaidRenderer renders fenced code blocks, special-casing the "mermaid"
// language and falling back to goldmark's standard rendering otherwise.
type mermaidRenderer struct{}
func (r *mermaidRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
}
func (r *mermaidRenderer) renderFencedCodeBlock(
w util.BufWriter, source []byte, node ast.Node, entering bool,
) (ast.WalkStatus, error) {
n := node.(*ast.FencedCodeBlock)
language := n.Language(source)
if bytes.Equal(language, mermaidLang) {
if entering {
_, _ = w.WriteString(``)
writeLinesEscaped(w, source, n)
} else {
_, _ = w.WriteString("\n")
}
return ast.WalkContinue, nil
}
if lexer := lexers.Get(string(language)); lexer != nil {
if entering {
if err := renderHighlighted(w, source, n, lexer); err != nil {
return ast.WalkStop, err
}
}
return ast.WalkContinue, nil
}
return renderDefaultFencedCodeBlock(w, source, n, entering)
}
// renderHighlighted writes a fenced code block as syntax-highlighted HTML
// using chroma, given a lexer already matched to the block's language tag.
// It writes the complete "...
" in one call (on the "entering"
// walk step); the corresponding "!entering" step is a no-op, mirroring how
// renderDefaultFencedCodeBlock already writes the opening tag and content
// together and only the closing tag separately.
func renderHighlighted(w util.BufWriter, source []byte, n *ast.FencedCodeBlock, lexer chroma.Lexer) error {
var code bytes.Buffer
lines := n.Lines()
for i := 0; i < lines.Len(); i++ {
line := lines.At(i)
code.Write(line.Value(source))
}
iterator, err := lexer.Tokenise(nil, code.String())
if err != nil {
return fmt.Errorf("tokenizing code block for syntax highlighting: %w", err)
}
if err := chromaFormatter.Format(w, chromaBaseStyle, iterator); err != nil {
return fmt.Errorf("formatting syntax-highlighted code block: %w", err)
}
return nil
}
// renderDefaultFencedCodeBlock reproduces goldmark's standard
// ...
rendering for
// non-Mermaid fenced code blocks.
func renderDefaultFencedCodeBlock(
w util.BufWriter, source []byte, n *ast.FencedCodeBlock, entering bool,
) (ast.WalkStatus, error) {
if entering {
_, _ = w.WriteString("')
writeLinesEscaped(w, source, n)
} else {
_, _ = w.WriteString("
\n")
}
return ast.WalkContinue, nil
}
// writeLinesEscaped writes a node's source lines HTML-escaped, matching
// goldmark's own code-block output. Escaping is safe here (and required, to
// prevent literal HTML injection from slide content) because a browser
// decodes entities back to their original characters before Mermaid reads
// an element's text content.
func writeLinesEscaped(w util.BufWriter, source []byte, n ast.Node) {
lines := n.Lines()
for i := 0; i < lines.Len(); i++ {
line := lines.At(i)
_, _ = w.Write(util.EscapeHTML(line.Value(source)))
}
}