markup/highlight: Rework the return value from HighlightCodeblock

To make it possible to render it with a custom HTML ("<div>")  wrapper.

Updates #9573
This commit is contained in:
Bjørn Erik Pedersen 2022-02-27 17:57:28 +01:00
parent 39261b689e
commit 3ad39001df
2 changed files with 118 additions and 23 deletions

View file

@ -113,6 +113,57 @@ Go Language: golang|
) )
} }
func TestHighlightCodeblock(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
[markup]
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
lineAnchors = ''
lineNoStart = 1
lineNos = false
lineNumbersInTable = true
noClasses = false
style = 'monokai'
tabWidth = 4
-- layouts/_default/_markup/render-codeblock.html --
{{ $result := transform.HighlightCodeBlock . }}
Inner: |{{ $result.Inner | safeHTML }}|
Wrapped: |{{ $result.Wrapped | safeHTML }}|
-- layouts/_default/single.html --
{{ .Content }}
-- content/p1.md --
---
title: "p1"
---
## Go Code
§§§go
fmt.Println("Hello, World!");
§§§
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b.AssertFileContent("public/p1/index.html",
"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>|",
"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>|",
)
}
func TestCodeChomp(t *testing.T) { func TestCodeChomp(t *testing.T) {
t.Parallel() t.Parallel()

View file

@ -75,7 +75,7 @@ func (h chromaHighlighter) Highlight(code, lang string, opts interface{}) (strin
} }
var b strings.Builder var b strings.Builder
if err := highlight(&b, code, lang, nil, cfg); err != nil { if _, _, err := highlight(&b, code, lang, nil, cfg); err != nil {
return "", err return "", err
} }
@ -103,13 +103,15 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts i
return HightlightResult{}, err return HightlightResult{}, err
} }
err := highlight(&b, ctx.Inner(), ctx.Type(), attributes, cfg) low, high, err := highlight(&b, ctx.Inner(), ctx.Type(), attributes, cfg)
if err != nil { if err != nil {
return HightlightResult{}, err return HightlightResult{}, err
} }
return HightlightResult{ return HightlightResult{
Body: template.HTML(b.String()), highlighted: template.HTML(b.String()),
innerLow: low,
innerHigh: high,
}, nil }, nil
} }
@ -127,7 +129,8 @@ func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Codebl
code := text.Puts(ctx.Inner()) code := text.Puts(ctx.Inner())
return highlight(w, code, ctx.Type(), attributes, cfg) _, _, err := highlight(w, code, ctx.Type(), attributes, cfg)
return err
} }
func (h chromaHighlighter) IsDefaultCodeBlockRenderer() bool { func (h chromaHighlighter) IsDefaultCodeBlockRenderer() bool {
@ -141,14 +144,22 @@ func (h chromaHighlighter) GetIdentity() identity.Identity {
} }
type HightlightResult struct { type HightlightResult struct {
Body template.HTML innerLow int
innerHigh int
highlighted template.HTML
} }
func (h HightlightResult) Highlighted() template.HTML { func (h HightlightResult) Wrapped() template.HTML {
return h.Body return h.highlighted
} }
func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) error { func (h HightlightResult) Inner() template.HTML {
return h.highlighted[h.innerLow:h.innerHigh]
}
func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) (int, int, error) {
var low, high int
var lexer chroma.Lexer var lexer chroma.Lexer
if lang != "" { if lang != "" {
lexer = lexers.Get(lang) lexer = lexers.Get(lang)
@ -162,12 +173,14 @@ func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.A
lang = strings.ToLower(lexer.Config().Name) lang = strings.ToLower(lexer.Config().Name)
} }
w := &byteCountFlexiWriter{delegate: fw}
if lexer == nil { if lexer == nil {
wrapper := getPreWrapper(lang) wrapper := getPreWrapper(lang, w)
fmt.Fprint(w, wrapper.Start(true, "")) fmt.Fprint(w, wrapper.Start(true, ""))
fmt.Fprint(w, gohtml.EscapeString(code)) fmt.Fprint(w, gohtml.EscapeString(code))
fmt.Fprint(w, wrapper.End(true)) fmt.Fprint(w, wrapper.End(true))
return nil return low, high, nil
} }
style := styles.Get(cfg.Style) style := styles.Get(cfg.Style)
@ -178,42 +191,44 @@ func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.A
iterator, err := lexer.Tokenise(nil, code) iterator, err := lexer.Tokenise(nil, code)
if err != nil { if err != nil {
return err return 0, 0, err
} }
options := cfg.ToHTMLOptions() options := cfg.ToHTMLOptions()
options = append(options, getHtmlPreWrapper(lang)) preWrapper := getPreWrapper(lang, w)
options = append(options, html.WithPreWrapper(preWrapper))
formatter := html.New(options...) formatter := html.New(options...)
writeDivStart(w, attributes) writeDivStart(w, attributes)
if err := formatter.Format(w, style, iterator); err != nil { if err := formatter.Format(w, style, iterator); err != nil {
return err return 0, 0, err
} }
writeDivEnd(w) writeDivEnd(w)
return nil return preWrapper.low, preWrapper.high, nil
} }
func getPreWrapper(language string) preWrapper { func getPreWrapper(language string, writeCounter *byteCountFlexiWriter) *preWrapper {
return preWrapper{language: language} return &preWrapper{language: language, writeCounter: writeCounter}
}
func getHtmlPreWrapper(language string) html.Option {
return html.WithPreWrapper(getPreWrapper(language))
} }
type preWrapper struct { type preWrapper struct {
low int
high int
writeCounter *byteCountFlexiWriter
language string language string
} }
func (p preWrapper) Start(code bool, styleAttr string) string { func (p *preWrapper) Start(code bool, styleAttr string) string {
var language string var language string
if code { if code {
language = p.language language = p.language
} }
w := &strings.Builder{} w := &strings.Builder{}
WritePreStart(w, language, styleAttr) WritePreStart(w, language, styleAttr)
p.low = p.writeCounter.counter + w.Len()
return w.String() return w.String()
} }
@ -229,7 +244,8 @@ func WritePreStart(w io.Writer, language, styleAttr string) {
const preEnd = "</code></pre>" const preEnd = "</code></pre>"
func (p preWrapper) End(code bool) string { func (p *preWrapper) End(code bool) string {
p.high = p.writeCounter.counter
return preEnd return preEnd
} }
@ -258,3 +274,31 @@ func writeDivStart(w hugio.FlexiWriter, attrs []attributes.Attribute) {
func writeDivEnd(w hugio.FlexiWriter) { func writeDivEnd(w hugio.FlexiWriter) {
w.WriteString("</div>") w.WriteString("</div>")
} }
type byteCountFlexiWriter struct {
delegate hugio.FlexiWriter
counter int
}
func (w *byteCountFlexiWriter) Write(p []byte) (int, error) {
n, err := w.delegate.Write(p)
w.counter += n
return n, err
}
func (w *byteCountFlexiWriter) WriteByte(c byte) error {
w.counter++
return w.delegate.WriteByte(c)
}
func (w *byteCountFlexiWriter) WriteString(s string) (int, error) {
n, err := w.delegate.WriteString(s)
w.counter += n
return n, err
}
func (w *byteCountFlexiWriter) WriteRune(r rune) (int, error) {
n, err := w.delegate.WriteRune(r)
w.counter += n
return n, err
}