Skip to content

Commit 3ad3900

Browse files
committed
markup/highlight: Rework the return value from HighlightCodeblock
To make it possible to render it with a custom HTML ("<div>") wrapper. Updates gohugoio#9573
1 parent 39261b6 commit 3ad3900

File tree

2 files changed

+118
-23
lines changed

2 files changed

+118
-23
lines changed

markup/goldmark/codeblocks/integration_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,57 @@ Go Language: golang|
113113
)
114114
}
115115

116+
func TestHighlightCodeblock(t *testing.T) {
117+
t.Parallel()
118+
119+
files := `
120+
-- config.toml --
121+
[markup]
122+
[markup.highlight]
123+
anchorLineNos = false
124+
codeFences = true
125+
guessSyntax = false
126+
hl_Lines = ''
127+
lineAnchors = ''
128+
lineNoStart = 1
129+
lineNos = false
130+
lineNumbersInTable = true
131+
noClasses = false
132+
style = 'monokai'
133+
tabWidth = 4
134+
-- layouts/_default/_markup/render-codeblock.html --
135+
{{ $result := transform.HighlightCodeBlock . }}
136+
Inner: |{{ $result.Inner | safeHTML }}|
137+
Wrapped: |{{ $result.Wrapped | safeHTML }}|
138+
-- layouts/_default/single.html --
139+
{{ .Content }}
140+
-- content/p1.md --
141+
---
142+
title: "p1"
143+
---
144+
145+
## Go Code
146+
147+
§§§go
148+
fmt.Println("Hello, World!");
149+
§§§
150+
151+
`
152+
153+
b := hugolib.NewIntegrationTestBuilder(
154+
hugolib.IntegrationTestConfig{
155+
T: t,
156+
TxtarString: files,
157+
NeedsOsFS: false,
158+
},
159+
).Build()
160+
161+
b.AssertFileContent("public/p1/index.html",
162+
"Inner: |<span class=\"line\"><span class=\"cl\"><span class=\"nx\">fmt</span><span class=\"p\">.</span><span class=\"nf\">Println</span><span class=\"p\">(</span><span class=\"s\">&#34;Hello, World!&#34;</span><span class=\"p\">);</span></span></span>|",
163+
"Wrapped: |<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-go\" data-lang=\"go\"><span class=\"line\"><span class=\"cl\"><span class=\"nx\">fmt</span><span class=\"p\">.</span><span class=\"nf\">Println</span><span class=\"p\">(</span><span class=\"s\">&#34;Hello, World!&#34;</span><span class=\"p\">);</span></span></span></code></pre></div>|",
164+
)
165+
}
166+
116167
func TestCodeChomp(t *testing.T) {
117168
t.Parallel()
118169

markup/highlight/highlight.go

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (h chromaHighlighter) Highlight(code, lang string, opts interface{}) (strin
7575
}
7676
var b strings.Builder
7777

78-
if err := highlight(&b, code, lang, nil, cfg); err != nil {
78+
if _, _, err := highlight(&b, code, lang, nil, cfg); err != nil {
7979
return "", err
8080
}
8181

@@ -103,13 +103,15 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts i
103103
return HightlightResult{}, err
104104
}
105105

106-
err := highlight(&b, ctx.Inner(), ctx.Type(), attributes, cfg)
106+
low, high, err := highlight(&b, ctx.Inner(), ctx.Type(), attributes, cfg)
107107
if err != nil {
108108
return HightlightResult{}, err
109109
}
110110

111111
return HightlightResult{
112-
Body: template.HTML(b.String()),
112+
highlighted: template.HTML(b.String()),
113+
innerLow: low,
114+
innerHigh: high,
113115
}, nil
114116
}
115117

@@ -127,7 +129,8 @@ func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Codebl
127129

128130
code := text.Puts(ctx.Inner())
129131

130-
return highlight(w, code, ctx.Type(), attributes, cfg)
132+
_, _, err := highlight(w, code, ctx.Type(), attributes, cfg)
133+
return err
131134
}
132135

133136
func (h chromaHighlighter) IsDefaultCodeBlockRenderer() bool {
@@ -141,14 +144,22 @@ func (h chromaHighlighter) GetIdentity() identity.Identity {
141144
}
142145

143146
type HightlightResult struct {
144-
Body template.HTML
147+
innerLow int
148+
innerHigh int
149+
highlighted template.HTML
145150
}
146151

147-
func (h HightlightResult) Highlighted() template.HTML {
148-
return h.Body
152+
func (h HightlightResult) Wrapped() template.HTML {
153+
return h.highlighted
149154
}
150155

151-
func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) error {
156+
func (h HightlightResult) Inner() template.HTML {
157+
return h.highlighted[h.innerLow:h.innerHigh]
158+
}
159+
160+
func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) (int, int, error) {
161+
var low, high int
162+
152163
var lexer chroma.Lexer
153164
if lang != "" {
154165
lexer = lexers.Get(lang)
@@ -162,12 +173,14 @@ func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.A
162173
lang = strings.ToLower(lexer.Config().Name)
163174
}
164175

176+
w := &byteCountFlexiWriter{delegate: fw}
177+
165178
if lexer == nil {
166-
wrapper := getPreWrapper(lang)
179+
wrapper := getPreWrapper(lang, w)
167180
fmt.Fprint(w, wrapper.Start(true, ""))
168181
fmt.Fprint(w, gohtml.EscapeString(code))
169182
fmt.Fprint(w, wrapper.End(true))
170-
return nil
183+
return low, high, nil
171184
}
172185

173186
style := styles.Get(cfg.Style)
@@ -178,42 +191,44 @@ func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.A
178191

179192
iterator, err := lexer.Tokenise(nil, code)
180193
if err != nil {
181-
return err
194+
return 0, 0, err
182195
}
183196

184197
options := cfg.ToHTMLOptions()
185-
options = append(options, getHtmlPreWrapper(lang))
198+
preWrapper := getPreWrapper(lang, w)
199+
options = append(options, html.WithPreWrapper(preWrapper))
186200

187201
formatter := html.New(options...)
188202

189203
writeDivStart(w, attributes)
204+
190205
if err := formatter.Format(w, style, iterator); err != nil {
191-
return err
206+
return 0, 0, err
192207
}
193208
writeDivEnd(w)
194209

195-
return nil
210+
return preWrapper.low, preWrapper.high, nil
196211
}
197212

198-
func getPreWrapper(language string) preWrapper {
199-
return preWrapper{language: language}
200-
}
201-
202-
func getHtmlPreWrapper(language string) html.Option {
203-
return html.WithPreWrapper(getPreWrapper(language))
213+
func getPreWrapper(language string, writeCounter *byteCountFlexiWriter) *preWrapper {
214+
return &preWrapper{language: language, writeCounter: writeCounter}
204215
}
205216

206217
type preWrapper struct {
207-
language string
218+
low int
219+
high int
220+
writeCounter *byteCountFlexiWriter
221+
language string
208222
}
209223

210-
func (p preWrapper) Start(code bool, styleAttr string) string {
224+
func (p *preWrapper) Start(code bool, styleAttr string) string {
211225
var language string
212226
if code {
213227
language = p.language
214228
}
215229
w := &strings.Builder{}
216230
WritePreStart(w, language, styleAttr)
231+
p.low = p.writeCounter.counter + w.Len()
217232
return w.String()
218233
}
219234

@@ -229,7 +244,8 @@ func WritePreStart(w io.Writer, language, styleAttr string) {
229244

230245
const preEnd = "</code></pre>"
231246

232-
func (p preWrapper) End(code bool) string {
247+
func (p *preWrapper) End(code bool) string {
248+
p.high = p.writeCounter.counter
233249
return preEnd
234250
}
235251

@@ -258,3 +274,31 @@ func writeDivStart(w hugio.FlexiWriter, attrs []attributes.Attribute) {
258274
func writeDivEnd(w hugio.FlexiWriter) {
259275
w.WriteString("</div>")
260276
}
277+
278+
type byteCountFlexiWriter struct {
279+
delegate hugio.FlexiWriter
280+
counter int
281+
}
282+
283+
func (w *byteCountFlexiWriter) Write(p []byte) (int, error) {
284+
n, err := w.delegate.Write(p)
285+
w.counter += n
286+
return n, err
287+
}
288+
289+
func (w *byteCountFlexiWriter) WriteByte(c byte) error {
290+
w.counter++
291+
return w.delegate.WriteByte(c)
292+
}
293+
294+
func (w *byteCountFlexiWriter) WriteString(s string) (int, error) {
295+
n, err := w.delegate.WriteString(s)
296+
w.counter += n
297+
return n, err
298+
}
299+
300+
func (w *byteCountFlexiWriter) WriteRune(r rune) (int, error) {
301+
n, err := w.delegate.WriteRune(r)
302+
w.counter += n
303+
return n, err
304+
}

0 commit comments

Comments
 (0)