hugo

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

git clone git://git.shimmy1996.com/hugo.git
commit 928a89696273b1a5defa0e85115c9bd0e167cbf0
parent 2e54c009331dc8c6260d16a55d4a13cfff55054d
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Sat, 26 Feb 2022 12:52:06 +0100

markup/goldmark: Add Position to CodeblockContext

But note that this is not particulary fast and the recommendad usage is error logging only.

Updates #9574

Diffstat:
Mhugolib/page__per_output.go | 21+++++++++++++++++++++
Mhugolib/site.go | 7++++++-
Mmarkup/converter/converter.go | 6+++++-
Mmarkup/converter/hooks/hooks.go | 14++++++++++++++
Mmarkup/goldmark/codeblocks/integration_test.go | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmarkup/goldmark/codeblocks/render.go | 62++++++++++++++++++++++++++++++++++++++++++++++----------------
Mmarkup/goldmark/codeblocks/transform.go | 1+
7 files changed, 160 insertions(+), 18 deletions(-)
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
@@ -23,6 +23,7 @@ import (
 	"sync"
 	"unicode/utf8"
 
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/mitchellh/mapstructure"
 	"github.com/pkg/errors"
@@ -430,6 +431,25 @@ func (p *pageContentOutput) initRenderHooks() error {
 		renderCache := make(map[cacheKey]interface{})
 		var renderCacheMu sync.Mutex
 
+		resolvePosition := func(ctx interface{}) text.Position {
+			var offset int
+
+			switch v := ctx.(type) {
+			case hooks.CodeblockContext:
+				offset = bytes.Index(p.p.source.parsed.Input(), []byte(v.Code()))
+			}
+
+			pos := p.p.posFromInput(p.p.source.parsed.Input(), offset)
+
+			if pos.LineNumber > 0 {
+				// Move up to the code fence delimiter.
+				// This is in line with how we report on shortcodes.
+				pos.LineNumber = pos.LineNumber - 1
+			}
+
+			return pos
+		}
+
 		p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} {
 			renderCacheMu.Lock()
 			defer renderCacheMu.Unlock()
@@ -510,6 +530,7 @@ func (p *pageContentOutput) initRenderHooks() error {
 				templateHandler: p.p.s.Tmpl(),
 				SearchProvider:  templ.(identity.SearchProvider),
 				templ:           templ,
+				resolvePosition: resolvePosition,
 			}
 			renderCache[key] = r
 			return r
diff --git a/hugolib/site.go b/hugolib/site.go
@@ -1778,7 +1778,8 @@ var infoOnMissingLayout = map[string]bool{
 type hookRendererTemplate struct {
 	templateHandler tpl.TemplateHandler
 	identity.SearchProvider
-	templ tpl.Template
+	templ           tpl.Template
+	resolvePosition func(ctx interface{}) text.Position
 }
 
 func (hr hookRendererTemplate) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
@@ -1793,6 +1794,10 @@ func (hr hookRendererTemplate) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.Co
 	return hr.templateHandler.Execute(hr.templ, w, ctx)
 }
 
+func (hr hookRendererTemplate) ResolvePosition(ctx interface{}) text.Position {
+	return hr.resolvePosition(ctx)
+}
+
 func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
 	if templ == nil {
 		s.logMissingLayout(name, "", "", outputFormat)
diff --git a/markup/converter/converter.go b/markup/converter/converter.go
@@ -128,9 +128,13 @@ type DocumentContext struct {
 
 // RenderContext holds contextual information about the content to render.
 type RenderContext struct {
-	Src       []byte
+	// Src is the content to render.
+	Src []byte
+
+	// Whether to render TableOfContents.
 	RenderTOC bool
 
+	// GerRenderer provides hook renderers on demand.
 	GetRenderer hooks.GetRendererFunc
 }
 
diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go
@@ -17,6 +17,7 @@ import (
 	"io"
 
 	"github.com/gohugoio/hugo/common/hugio"
+	"github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/identity"
 	"github.com/gohugoio/hugo/markup/internal/attributes"
 )
@@ -37,6 +38,7 @@ type LinkContext interface {
 
 type CodeblockContext interface {
 	AttributesProvider
+	text.Positioner
 	Options() map[string]interface{}
 	Lang() string
 	Code() string
@@ -59,6 +61,10 @@ type CodeBlockRenderer interface {
 	identity.Provider
 }
 
+type IsDefaultCodeBlockRendererProvider interface {
+	IsDefaultCodeBlockRenderer() bool
+}
+
 // HeadingContext contains accessors to all attributes that a HeadingRenderer
 // can use to render a heading.
 type HeadingContext interface {
@@ -84,6 +90,14 @@ type HeadingRenderer interface {
 	identity.Provider
 }
 
+// ElementPositionRevolver provides a way to resolve the start Position
+// of a markdown element in the original source document.
+// This may be both slow and aproximate, so should only be
+// used for error logging.
+type ElementPositionRevolver interface {
+	ResolvePosition(ctx interface{}) text.Position
+}
+
 type RendererType int
 
 const (
diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go
@@ -141,3 +141,70 @@ echo "p1";
 
 	b.AssertFileContent("public/p1/index.html", "|echo \"p1\";|")
 }
+
+func TestCodePosition(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+##   Code
+
+§§§
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Position: {{ .Position | safeHTML }}
+
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", "Position: \"content/p1.md:7:1\"")
+}
+
+// Issue 9571
+func TestOptionsNonChroma(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+-- content/p1.md --
+---
+title: "p1"
+---
+
+##   Code
+
+§§§bash {style=monokai}
+echo "p1";
+§§§
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Style: {{ .Attributes }}|
+
+
+`
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/p1/index.html", "asdfadf")
+}
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
@@ -16,7 +16,9 @@ package codeblocks
 import (
 	"bytes"
 	"fmt"
+	"sync"
 
+	"github.com/gohugoio/hugo/common/herrors"
 	htext "github.com/gohugoio/hugo/common/text"
 	"github.com/gohugoio/hugo/markup/converter/hooks"
 	"github.com/gohugoio/hugo/markup/goldmark/internal/render"
@@ -59,6 +61,8 @@ func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 }
 
 func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+	defer herrors.Recover()
+
 	ctx := w.(*render.Context)
 
 	if entering {
@@ -67,6 +71,11 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 
 	n := node.(*codeBlock)
 	lang := string(n.b.Language(src))
+	renderer := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
+	if renderer == nil {
+		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
+	}
+
 	ordinal := n.ordinal
 
 	var buff bytes.Buffer
@@ -77,30 +86,37 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
 		buff.Write(line.Value(src))
 	}
 
-	text := htext.Chomp(buff.String())
+	s := htext.Chomp(buff.String())
 
 	var info []byte
 	if n.b.Info != nil {
 		info = n.b.Info.Segment.Value(src)
 	}
 	attrs := getAttributes(n.b, info)
+	cbctx := &codeBlockContext{
+		page:             ctx.DocumentContext().Document,
+		lang:             lang,
+		code:             s,
+		ordinal:          ordinal,
+		AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
+	}
 
-	v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
-	if v == nil {
-		return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
+	cbctx.createPos = func() htext.Position {
+		if resolver, ok := renderer.(hooks.ElementPositionRevolver); ok {
+			return resolver.ResolvePosition(cbctx)
+		}
+		return htext.Position{
+			Filename:     ctx.DocumentContext().Filename,
+			LineNumber:   0,
+			ColumnNumber: 0,
+		}
 	}
 
-	cr := v.(hooks.CodeBlockRenderer)
+	cr := renderer.(hooks.CodeBlockRenderer)
 
 	err := cr.RenderCodeblock(
 		w,
-		codeBlockContext{
-			page:             ctx.DocumentContext().Document,
-			lang:             lang,
-			code:             text,
-			ordinal:          ordinal,
-			AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
-		},
+		cbctx,
 	)
 
 	ctx.AddIdentity(cr)
@@ -113,25 +129,39 @@ type codeBlockContext struct {
 	lang    string
 	code    string
 	ordinal int
+
+	// This is only used in error situations and is expensive to create,
+	// to deleay creation until needed.
+	pos       htext.Position
+	posInit   sync.Once
+	createPos func() htext.Position
+
 	*attributes.AttributesHolder
 }
 
-func (c codeBlockContext) Page() interface{} {
+func (c *codeBlockContext) Page() interface{} {
 	return c.page
 }
 
-func (c codeBlockContext) Lang() string {
+func (c *codeBlockContext) Lang() string {
 	return c.lang
 }
 
-func (c codeBlockContext) Code() string {
+func (c *codeBlockContext) Code() string {
 	return c.code
 }
 
-func (c codeBlockContext) Ordinal() int {
+func (c *codeBlockContext) Ordinal() int {
 	return c.ordinal
 }
 
+func (c *codeBlockContext) Position() htext.Position {
+	c.posInit.Do(func() {
+		c.pos = c.createPos()
+	})
+	return c.pos
+}
+
 func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
 	if node.Attributes() != nil {
 		return node.Attributes()
diff --git a/markup/goldmark/codeblocks/transform.go b/markup/goldmark/codeblocks/transform.go
@@ -40,6 +40,7 @@ func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser
 		}
 
 		codeBlocks = append(codeBlocks, cb)
+
 		return ast.WalkContinue, nil
 	})