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))) } }