hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit 3ad39001df84c01a5da8ec7e008ee3835e1a7c4e
parent 39261b689e47116de5cfd3bee6b6e3af57deb97c
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Sun, 27 Feb 2022 17:57:28 +0100

markup/highlight: Rework the return value from HighlightCodeblock

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

Updates #9573

Diffstat:
Mmarkup/goldmark/codeblocks/integration_test.go | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mmarkup/highlight/highlight.go | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 118 insertions(+), 23 deletions(-)
diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go
@@ -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) {
 	t.Parallel()
 
diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go
@@ -75,7 +75,7 @@ func (h chromaHighlighter) Highlight(code, lang string, opts interface{}) (strin
 	}
 	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
 	}
 
@@ -103,13 +103,15 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts i
 		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 {
 		return HightlightResult{}, err
 	}
 
 	return HightlightResult{
-		Body: template.HTML(b.String()),
+		highlighted: template.HTML(b.String()),
+		innerLow:    low,
+		innerHigh:   high,
 	}, nil
 }
 
@@ -127,7 +129,8 @@ func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Codebl
 
 	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 {
@@ -141,14 +144,22 @@ func (h chromaHighlighter) GetIdentity() identity.Identity {
 }
 
 type HightlightResult struct {
-	Body template.HTML
+	innerLow    int
+	innerHigh   int
+	highlighted template.HTML
 }
 
-func (h HightlightResult) Highlighted() template.HTML {
-	return h.Body
+func (h HightlightResult) Wrapped() template.HTML {
+	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
 	if 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)
 	}
 
+	w := &byteCountFlexiWriter{delegate: fw}
+
 	if lexer == nil {
-		wrapper := getPreWrapper(lang)
+		wrapper := getPreWrapper(lang, w)
 		fmt.Fprint(w, wrapper.Start(true, ""))
 		fmt.Fprint(w, gohtml.EscapeString(code))
 		fmt.Fprint(w, wrapper.End(true))
-		return nil
+		return low, high, nil
 	}
 
 	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)
 	if err != nil {
-		return err
+		return 0, 0, err
 	}
 
 	options := cfg.ToHTMLOptions()
-	options = append(options, getHtmlPreWrapper(lang))
+	preWrapper := getPreWrapper(lang, w)
+	options = append(options, html.WithPreWrapper(preWrapper))
 
 	formatter := html.New(options...)
 
 	writeDivStart(w, attributes)
+
 	if err := formatter.Format(w, style, iterator); err != nil {
-		return err
+		return 0, 0, err
 	}
 	writeDivEnd(w)
 
-	return nil
+	return preWrapper.low, preWrapper.high, nil
 }
 
-func getPreWrapper(language string) preWrapper {
-	return preWrapper{language: language}
-}
-
-func getHtmlPreWrapper(language string) html.Option {
-	return html.WithPreWrapper(getPreWrapper(language))
+func getPreWrapper(language string, writeCounter *byteCountFlexiWriter) *preWrapper {
+	return &preWrapper{language: language, writeCounter: writeCounter}
 }
 
 type preWrapper struct {
-	language string
+	low          int
+	high         int
+	writeCounter *byteCountFlexiWriter
+	language     string
 }
 
-func (p preWrapper) Start(code bool, styleAttr string) string {
+func (p *preWrapper) Start(code bool, styleAttr string) string {
 	var language string
 	if code {
 		language = p.language
 	}
 	w := &strings.Builder{}
 	WritePreStart(w, language, styleAttr)
+	p.low = p.writeCounter.counter + w.Len()
 	return w.String()
 }
 
@@ -229,7 +244,8 @@ func WritePreStart(w io.Writer, language, styleAttr string) {
 
 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
 }
 
@@ -258,3 +274,31 @@ func writeDivStart(w hugio.FlexiWriter, attrs []attributes.Attribute) {
 func writeDivEnd(w hugio.FlexiWriter) {
 	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
+}