Skip to content

Commit

Permalink
markup/goldmark: Add Position to CodeblockContext
Browse files Browse the repository at this point in the history
But note that this is not particulary fast and the recommendad usage is error logging only.

Updates gohugoio#9574
  • Loading branch information
bep committed Feb 26, 2022
1 parent f04577d commit 748b7ae
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 14 deletions.
22 changes: 22 additions & 0 deletions hugolib/page__per_output.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -551,6 +572,7 @@ func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, c
r, err := c.Convert(
converter.RenderContext{
Src: content,
SrcOffset: cp.p.source.posMainContent,
RenderTOC: renderTOC,
GetRenderer: cp.renderHooks.getRenderer,
})
Expand Down
7 changes: 6 additions & 1 deletion hugolib/site.go
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion markup/converter/converter.go
Expand Up @@ -128,9 +128,17 @@ 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

// SrcOffset is the offset in bytes for the source content in the original document.
// This is only used in error messages to get accurate line numbers.
SrcOffset int

// Whether to render TableOfContents.
RenderTOC bool

// GerRenderer provides hook renderers on demand.
GetRenderer hooks.GetRendererFunc
}

Expand Down
10 changes: 10 additions & 0 deletions markup/converter/hooks/hooks.go
Expand Up @@ -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"
)
Expand All @@ -37,6 +38,7 @@ type LinkContext interface {

type CodeblockContext interface {
AttributesProvider
text.Positioner
Options() map[string]interface{}
Lang() string
Code() string
Expand Down Expand Up @@ -84,6 +86,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 (
Expand Down
33 changes: 33 additions & 0 deletions markup/goldmark/codeblocks/integration_test.go
Expand Up @@ -141,3 +141,36 @@ 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\"")
}
54 changes: 42 additions & 12 deletions markup/goldmark/codeblocks/render.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -113,6 +129,13 @@ 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
}

Expand All @@ -132,6 +155,13 @@ 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()
Expand Down
1 change: 1 addition & 0 deletions markup/goldmark/codeblocks/transform.go
Expand Up @@ -40,6 +40,7 @@ func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser
}

codeBlocks = append(codeBlocks, cb)

return ast.WalkContinue, nil
})

Expand Down

0 comments on commit 748b7ae

Please sign in to comment.