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 #9574
  • Loading branch information
bep committed Feb 26, 2022
1 parent 2e54c00 commit 928a896
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 18 deletions.
21 changes: 21 additions & 0 deletions hugolib/page__per_output.go
Original file line number Diff line number Diff line change
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
7 changes: 6 additions & 1 deletion hugolib/site.go
Original file line number Diff line number Diff line change
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
6 changes: 5 additions & 1 deletion markup/converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
14 changes: 14 additions & 0 deletions markup/converter/hooks/hooks.go
Original file line number Diff line number Diff line change
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 All @@ -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 {
Expand All @@ -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 (
Expand Down
67 changes: 67 additions & 0 deletions markup/goldmark/codeblocks/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
62 changes: 46 additions & 16 deletions markup/goldmark/codeblocks/render.go
Original file line number Diff line number Diff line change
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,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()
Expand Down
1 change: 1 addition & 0 deletions markup/goldmark/codeblocks/transform.go
Original file line number Diff line number Diff line change
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 928a896

Please sign in to comment.