mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
markup/goldmark: Add Position to CodeblockContext
But note that this is not particulary fast and the recommendad usage is error logging only. Updates #9574
This commit is contained in:
parent
2e54c00933
commit
928a896962
7 changed files with 161 additions and 19 deletions
|
@ -23,6 +23,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -430,6 +431,25 @@ func (p *pageContentOutput) initRenderHooks() error {
|
||||||
renderCache := make(map[cacheKey]interface{})
|
renderCache := make(map[cacheKey]interface{})
|
||||||
var renderCacheMu sync.Mutex
|
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{} {
|
p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} {
|
||||||
renderCacheMu.Lock()
|
renderCacheMu.Lock()
|
||||||
defer renderCacheMu.Unlock()
|
defer renderCacheMu.Unlock()
|
||||||
|
@ -510,6 +530,7 @@ func (p *pageContentOutput) initRenderHooks() error {
|
||||||
templateHandler: p.p.s.Tmpl(),
|
templateHandler: p.p.s.Tmpl(),
|
||||||
SearchProvider: templ.(identity.SearchProvider),
|
SearchProvider: templ.(identity.SearchProvider),
|
||||||
templ: templ,
|
templ: templ,
|
||||||
|
resolvePosition: resolvePosition,
|
||||||
}
|
}
|
||||||
renderCache[key] = r
|
renderCache[key] = r
|
||||||
return r
|
return r
|
||||||
|
|
|
@ -1778,7 +1778,8 @@ var infoOnMissingLayout = map[string]bool{
|
||||||
type hookRendererTemplate struct {
|
type hookRendererTemplate struct {
|
||||||
templateHandler tpl.TemplateHandler
|
templateHandler tpl.TemplateHandler
|
||||||
identity.SearchProvider
|
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 {
|
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)
|
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) {
|
func (s *Site) renderForTemplate(name, outputFormat string, d interface{}, w io.Writer, templ tpl.Template) (err error) {
|
||||||
if templ == nil {
|
if templ == nil {
|
||||||
s.logMissingLayout(name, "", "", outputFormat)
|
s.logMissingLayout(name, "", "", outputFormat)
|
||||||
|
|
|
@ -128,9 +128,13 @@ type DocumentContext struct {
|
||||||
|
|
||||||
// RenderContext holds contextual information about the content to render.
|
// RenderContext holds contextual information about the content to render.
|
||||||
type RenderContext struct {
|
type RenderContext struct {
|
||||||
Src []byte
|
// Src is the content to render.
|
||||||
|
Src []byte
|
||||||
|
|
||||||
|
// Whether to render TableOfContents.
|
||||||
RenderTOC bool
|
RenderTOC bool
|
||||||
|
|
||||||
|
// GerRenderer provides hook renderers on demand.
|
||||||
GetRenderer hooks.GetRendererFunc
|
GetRenderer hooks.GetRendererFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
|
"github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/markup/internal/attributes"
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
)
|
)
|
||||||
|
@ -37,6 +38,7 @@ type LinkContext interface {
|
||||||
|
|
||||||
type CodeblockContext interface {
|
type CodeblockContext interface {
|
||||||
AttributesProvider
|
AttributesProvider
|
||||||
|
text.Positioner
|
||||||
Options() map[string]interface{}
|
Options() map[string]interface{}
|
||||||
Lang() string
|
Lang() string
|
||||||
Code() string
|
Code() string
|
||||||
|
@ -59,6 +61,10 @@ type CodeBlockRenderer interface {
|
||||||
identity.Provider
|
identity.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IsDefaultCodeBlockRendererProvider interface {
|
||||||
|
IsDefaultCodeBlockRenderer() bool
|
||||||
|
}
|
||||||
|
|
||||||
// HeadingContext contains accessors to all attributes that a HeadingRenderer
|
// HeadingContext contains accessors to all attributes that a HeadingRenderer
|
||||||
// can use to render a heading.
|
// can use to render a heading.
|
||||||
type HeadingContext interface {
|
type HeadingContext interface {
|
||||||
|
@ -84,6 +90,14 @@ type HeadingRenderer interface {
|
||||||
identity.Provider
|
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
|
type RendererType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -141,3 +141,70 @@ echo "p1";
|
||||||
|
|
||||||
b.AssertFileContent("public/p1/index.html", "|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")
|
||||||
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@ package codeblocks
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
htext "github.com/gohugoio/hugo/common/text"
|
htext "github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
"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) {
|
func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
defer herrors.Recover()
|
||||||
|
|
||||||
ctx := w.(*render.Context)
|
ctx := w.(*render.Context)
|
||||||
|
|
||||||
if entering {
|
if entering {
|
||||||
|
@ -67,6 +71,11 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
|
||||||
|
|
||||||
n := node.(*codeBlock)
|
n := node.(*codeBlock)
|
||||||
lang := string(n.b.Language(src))
|
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
|
ordinal := n.ordinal
|
||||||
|
|
||||||
var buff bytes.Buffer
|
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))
|
buff.Write(line.Value(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
text := htext.Chomp(buff.String())
|
s := htext.Chomp(buff.String())
|
||||||
|
|
||||||
var info []byte
|
var info []byte
|
||||||
if n.b.Info != nil {
|
if n.b.Info != nil {
|
||||||
info = n.b.Info.Segment.Value(src)
|
info = n.b.Info.Segment.Value(src)
|
||||||
}
|
}
|
||||||
attrs := getAttributes(n.b, info)
|
attrs := getAttributes(n.b, info)
|
||||||
|
cbctx := &codeBlockContext{
|
||||||
v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
|
page: ctx.DocumentContext().Document,
|
||||||
if v == nil {
|
lang: lang,
|
||||||
return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
|
code: s,
|
||||||
|
ordinal: ordinal,
|
||||||
|
AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
|
||||||
}
|
}
|
||||||
|
|
||||||
cr := v.(hooks.CodeBlockRenderer)
|
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 := renderer.(hooks.CodeBlockRenderer)
|
||||||
|
|
||||||
err := cr.RenderCodeblock(
|
err := cr.RenderCodeblock(
|
||||||
w,
|
w,
|
||||||
codeBlockContext{
|
cbctx,
|
||||||
page: ctx.DocumentContext().Document,
|
|
||||||
lang: lang,
|
|
||||||
code: text,
|
|
||||||
ordinal: ordinal,
|
|
||||||
AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.AddIdentity(cr)
|
ctx.AddIdentity(cr)
|
||||||
|
@ -113,25 +129,39 @@ type codeBlockContext struct {
|
||||||
lang string
|
lang string
|
||||||
code string
|
code string
|
||||||
ordinal int
|
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
|
*attributes.AttributesHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c codeBlockContext) Page() interface{} {
|
func (c *codeBlockContext) Page() interface{} {
|
||||||
return c.page
|
return c.page
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c codeBlockContext) Lang() string {
|
func (c *codeBlockContext) Lang() string {
|
||||||
return c.lang
|
return c.lang
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c codeBlockContext) Code() string {
|
func (c *codeBlockContext) Code() string {
|
||||||
return c.code
|
return c.code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c codeBlockContext) Ordinal() int {
|
func (c *codeBlockContext) Ordinal() int {
|
||||||
return c.ordinal
|
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 {
|
func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
|
||||||
if node.Attributes() != nil {
|
if node.Attributes() != nil {
|
||||||
return node.Attributes()
|
return node.Attributes()
|
||||||
|
|
|
@ -40,6 +40,7 @@ func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser
|
||||||
}
|
}
|
||||||
|
|
||||||
codeBlocks = append(codeBlocks, cb)
|
codeBlocks = append(codeBlocks, cb)
|
||||||
|
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue