1// based on HUGO markup/codeblocks/render.go -- Pet
2
3// Copyright 2024 The Hugo Authors. All rights reserved.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16package goldmark_extensions
17
18import (
19 "bytes"
20 "fmt"
21 "html"
22 "io"
23 "strings"
24
25 "github.com/yuin/goldmark"
26 "github.com/yuin/goldmark/ast"
27 "github.com/yuin/goldmark/renderer"
28 "github.com/yuin/goldmark/util"
29
30 html_formatter "github.com/alecthomas/chroma/v2/formatters/html"
31 "github.com/alecthomas/chroma/v2/lexers"
32 "github.com/alecthomas/chroma/v2/styles"
33)
34
35const
36 ChromaStyle string = "colorful"
37
38func Highlight(lang, code string, w io.Writer, with_line_numbers bool) {
39/*
40 * code highlighter
41 * lang can be either language or file name
42 */
43 if lang == "" {
44 fmt.Fprintf(w, "<pre><code>%s</code></pre>", html.EscapeString(code))
45 return
46 }
47
48 lexer := lexers.Get(lang)
49 if lexer == nil {
50 lexer = lexers.Match(lang)
51 if lexer == nil {
52 lexer = lexers.Fallback
53 }
54 }
55
56 formatter := html_formatter.New(html_formatter.WithClasses(true), html_formatter.WithLineNumbers(with_line_numbers))
57
58 iterator, err := lexer.Tokenise(nil, code)
59 if err != nil {
60 panic(err)
61 }
62 style := styles.Get(ChromaStyle)
63 if style == nil {
64 style = styles.Fallback
65 }
66 err = formatter.Format(w, style, iterator)
67 if err != nil {
68 panic(err)
69 }
70}
71
72func Chomp(s string) string {
73 /*
74 * remove trailing newline characters from s.
75 */
76 return strings.TrimRightFunc(s, func(r rune) bool {
77 return r == '\n' || r == '\r'
78 })
79}
80
81type (
82 codeBlocksExtension struct{}
83 htmlRenderer struct{}
84)
85
86var CodeBlocksExtension = &codeBlocksExtension{}
87
88func (e *codeBlocksExtension) Extend(m goldmark.Markdown) {
89 m.Renderer().AddOptions(renderer.WithNodeRenderers(
90 util.Prioritized(newHTMLRenderer(), 100),
91 ))
92}
93
94func newHTMLRenderer() renderer.NodeRenderer {
95 r := &htmlRenderer{}
96 return r
97}
98
99func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
100 reg.Register(ast.KindFencedCodeBlock, r.renderCodeBlock)
101}
102
103func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
104
105 if entering {
106 return ast.WalkContinue, nil
107 }
108
109 n := node.(*ast.FencedCodeBlock)
110
111 lang := getLang(n, src)
112
113 var buff bytes.Buffer
114
115 l := n.Lines().Len()
116 for i := range l {
117 line := n.Lines().At(i)
118 buff.Write(line.Value(src))
119 }
120 codeblock := Chomp(buff.String())
121 Highlight(lang, codeblock, w, false)
122 return ast.WalkContinue, nil
123}
124
125func getLang(node *ast.FencedCodeBlock, src []byte) string {
126 langWithAttributes := string(node.Language(src))
127 lang, _, _ := strings.Cut(langWithAttributes, "{")
128 return lang
129}