summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-26 12:52:06 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2022-02-26 21:54:36 +0100
commit928a89696273b1a5defa0e85115c9bd0e167cbf0 (patch)
tree3c9eb330385a01e230036dc018817cb50e65cbef
parent2e54c009331dc8c6260d16a55d4a13cfff55054d (diff)
markup/goldmark: Add Position to CodeblockContext
But note that this is not particulary fast and the recommendad usage is error logging only. Updates #9574
-rw-r--r--hugolib/page__per_output.go21
-rw-r--r--hugolib/site.go7
-rw-r--r--markup/converter/converter.go6
-rw-r--r--markup/converter/hooks/hooks.go14
-rw-r--r--markup/goldmark/codeblocks/integration_test.go67
-rw-r--r--markup/goldmark/codeblocks/render.go62
-rw-r--r--markup/goldmark/codeblocks/transform.go1
7 files changed, 160 insertions, 18 deletions
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index 29beb672e..4067e827f 100644
--- 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
index d78a4e10c..ebda29f46 100644
--- 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
index 30addfec6..dac0a6b73 100644
--- 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
index 987cb1dc3..a570113ff 100644
--- 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
index 01510fc81..68f6b809e 100644
--- 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
index 6cc43128b..bbf15bef3 100644
--- 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
index 791e99a5c..be5334b5f 100644
--- 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
})