mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	markup/highlight: Replace the temp for with a dependency
This commit is contained in:
		@@ -22,7 +22,7 @@ import (
 | 
			
		||||
	"github.com/alecthomas/chroma/formatters/html"
 | 
			
		||||
	"github.com/alecthomas/chroma/lexers"
 | 
			
		||||
	"github.com/alecthomas/chroma/styles"
 | 
			
		||||
	hl "github.com/gohugoio/hugo/markup/highlight/temphighlighting"
 | 
			
		||||
	hl "github.com/yuin/goldmark-highlighting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func New(cfg Config) Highlighter {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,512 +0,0 @@
 | 
			
		||||
// package highlighting is a extension for the goldmark(http://github.com/yuin/goldmark).
 | 
			
		||||
//
 | 
			
		||||
// This extension adds syntax-highlighting to the fenced code blocks using
 | 
			
		||||
// chroma(https://github.com/alecthomas/chroma).
 | 
			
		||||
//
 | 
			
		||||
// TODO(bep) this is a very temporary fork based on https://github.com/yuin/goldmark-highlighting/pull/10
 | 
			
		||||
// MIT Licensed, Copyright Yusuke Inuzuka
 | 
			
		||||
package temphighlighting
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/yuin/goldmark"
 | 
			
		||||
	"github.com/yuin/goldmark/ast"
 | 
			
		||||
	"github.com/yuin/goldmark/parser"
 | 
			
		||||
	"github.com/yuin/goldmark/renderer"
 | 
			
		||||
	"github.com/yuin/goldmark/renderer/html"
 | 
			
		||||
	"github.com/yuin/goldmark/text"
 | 
			
		||||
	"github.com/yuin/goldmark/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/alecthomas/chroma"
 | 
			
		||||
	chromahtml "github.com/alecthomas/chroma/formatters/html"
 | 
			
		||||
	"github.com/alecthomas/chroma/lexers"
 | 
			
		||||
	"github.com/alecthomas/chroma/styles"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ImmutableAttributes is a read-only interface for ast.Attributes.
 | 
			
		||||
type ImmutableAttributes interface {
 | 
			
		||||
	// Get returns (value, true) if an attribute associated with given
 | 
			
		||||
	// name exists, otherwise (nil, false)
 | 
			
		||||
	Get(name []byte) (interface{}, bool)
 | 
			
		||||
 | 
			
		||||
	// GetString returns (value, true) if an attribute associated with given
 | 
			
		||||
	// name exists, otherwise (nil, false)
 | 
			
		||||
	GetString(name string) (interface{}, bool)
 | 
			
		||||
 | 
			
		||||
	// All returns all attributes.
 | 
			
		||||
	All() []ast.Attribute
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type immutableAttributes struct {
 | 
			
		||||
	n ast.Node
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *immutableAttributes) Get(name []byte) (interface{}, bool) {
 | 
			
		||||
	return a.n.Attribute(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *immutableAttributes) GetString(name string) (interface{}, bool) {
 | 
			
		||||
	return a.n.AttributeString(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *immutableAttributes) All() []ast.Attribute {
 | 
			
		||||
	if a.n.Attributes() == nil {
 | 
			
		||||
		return []ast.Attribute{}
 | 
			
		||||
	}
 | 
			
		||||
	return a.n.Attributes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CodeBlockContext holds contextual information of code highlighting.
 | 
			
		||||
type CodeBlockContext interface {
 | 
			
		||||
	// Language returns (language, true) if specified, otherwise (nil, false).
 | 
			
		||||
	Language() ([]byte, bool)
 | 
			
		||||
 | 
			
		||||
	// Highlighted returns true if this code block can be highlighted, otherwise false.
 | 
			
		||||
	Highlighted() bool
 | 
			
		||||
 | 
			
		||||
	// Attributes return attributes of the code block.
 | 
			
		||||
	Attributes() ImmutableAttributes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type codeBlockContext struct {
 | 
			
		||||
	language    []byte
 | 
			
		||||
	highlighted bool
 | 
			
		||||
	attributes  ImmutableAttributes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newCodeBlockContext(language []byte, highlighted bool, attrs ImmutableAttributes) CodeBlockContext {
 | 
			
		||||
	return &codeBlockContext{
 | 
			
		||||
		language:    language,
 | 
			
		||||
		highlighted: highlighted,
 | 
			
		||||
		attributes:  attrs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *codeBlockContext) Language() ([]byte, bool) {
 | 
			
		||||
	if c.language != nil {
 | 
			
		||||
		return c.language, true
 | 
			
		||||
	}
 | 
			
		||||
	return nil, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *codeBlockContext) Highlighted() bool {
 | 
			
		||||
	return c.highlighted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *codeBlockContext) Attributes() ImmutableAttributes {
 | 
			
		||||
	return c.attributes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WrapperRenderer renders wrapper elements like div, pre, etc.
 | 
			
		||||
type WrapperRenderer func(w util.BufWriter, context CodeBlockContext, entering bool)
 | 
			
		||||
 | 
			
		||||
// CodeBlockOptions creates Chroma options per code block.
 | 
			
		||||
type CodeBlockOptions func(ctx CodeBlockContext) []chromahtml.Option
 | 
			
		||||
 | 
			
		||||
// Config struct holds options for the extension.
 | 
			
		||||
type Config struct {
 | 
			
		||||
	html.Config
 | 
			
		||||
 | 
			
		||||
	// Style is a highlighting style.
 | 
			
		||||
	// Supported styles are defined under https://github.com/alecthomas/chroma/tree/master/formatters.
 | 
			
		||||
	Style string
 | 
			
		||||
 | 
			
		||||
	// FormatOptions is a option related to output formats.
 | 
			
		||||
	// See https://github.com/alecthomas/chroma#the-html-formatter for details.
 | 
			
		||||
	FormatOptions []chromahtml.Option
 | 
			
		||||
 | 
			
		||||
	// CSSWriter is an io.Writer that will be used as CSS data output buffer.
 | 
			
		||||
	// If WithClasses() is enabled, you can get CSS data corresponds to the style.
 | 
			
		||||
	CSSWriter io.Writer
 | 
			
		||||
 | 
			
		||||
	// CodeBlockOptions allows set Chroma options per code block.
 | 
			
		||||
	CodeBlockOptions CodeBlockOptions
 | 
			
		||||
 | 
			
		||||
	// WrapperRendererCodeBlockOptions allows you to change wrapper elements.
 | 
			
		||||
	WrapperRenderer WrapperRenderer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig returns a new Config with defaults.
 | 
			
		||||
func NewConfig() Config {
 | 
			
		||||
	return Config{
 | 
			
		||||
		Config:           html.NewConfig(),
 | 
			
		||||
		Style:            "github",
 | 
			
		||||
		FormatOptions:    []chromahtml.Option{},
 | 
			
		||||
		CSSWriter:        nil,
 | 
			
		||||
		WrapperRenderer:  nil,
 | 
			
		||||
		CodeBlockOptions: nil,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetOption implements renderer.SetOptioner.
 | 
			
		||||
func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
 | 
			
		||||
	switch name {
 | 
			
		||||
	case optStyle:
 | 
			
		||||
		c.Style = value.(string)
 | 
			
		||||
	case optFormatOptions:
 | 
			
		||||
		if value != nil {
 | 
			
		||||
			c.FormatOptions = value.([]chromahtml.Option)
 | 
			
		||||
		}
 | 
			
		||||
	case optCSSWriter:
 | 
			
		||||
		c.CSSWriter = value.(io.Writer)
 | 
			
		||||
	case optWrapperRenderer:
 | 
			
		||||
		c.WrapperRenderer = value.(WrapperRenderer)
 | 
			
		||||
	case optCodeBlockOptions:
 | 
			
		||||
		c.CodeBlockOptions = value.(CodeBlockOptions)
 | 
			
		||||
	default:
 | 
			
		||||
		c.Config.SetOption(name, value)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Option interface is a functional option interface for the extension.
 | 
			
		||||
type Option interface {
 | 
			
		||||
	renderer.Option
 | 
			
		||||
	// SetHighlightingOption sets given option to the extension.
 | 
			
		||||
	SetHighlightingOption(*Config)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type withHTMLOptions struct {
 | 
			
		||||
	value []html.Option
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withHTMLOptions) SetConfig(c *renderer.Config) {
 | 
			
		||||
	if o.value != nil {
 | 
			
		||||
		for _, v := range o.value {
 | 
			
		||||
			v.(renderer.Option).SetConfig(c)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withHTMLOptions) SetHighlightingOption(c *Config) {
 | 
			
		||||
	if o.value != nil {
 | 
			
		||||
		for _, v := range o.value {
 | 
			
		||||
			v.SetHTMLOption(&c.Config)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
 | 
			
		||||
func WithHTMLOptions(opts ...html.Option) Option {
 | 
			
		||||
	return &withHTMLOptions{opts}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optStyle renderer.OptionName = "HighlightingStyle"
 | 
			
		||||
 | 
			
		||||
var highlightLinesAttrName = []byte("hl_lines")
 | 
			
		||||
 | 
			
		||||
var styleAttrName = []byte("hl_style")
 | 
			
		||||
var nohlAttrName = []byte("nohl")
 | 
			
		||||
var linenosAttrName = []byte("linenos")
 | 
			
		||||
var linenosTableAttrValue = []byte("table")
 | 
			
		||||
var linenosInlineAttrValue = []byte("inline")
 | 
			
		||||
var linenostartAttrName = []byte("linenostart")
 | 
			
		||||
 | 
			
		||||
type withStyle struct {
 | 
			
		||||
	value string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withStyle) SetConfig(c *renderer.Config) {
 | 
			
		||||
	c.Options[optStyle] = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withStyle) SetHighlightingOption(c *Config) {
 | 
			
		||||
	c.Style = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithStyle is a functional option that changes highlighting style.
 | 
			
		||||
func WithStyle(style string) Option {
 | 
			
		||||
	return &withStyle{style}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optCSSWriter renderer.OptionName = "HighlightingCSSWriter"
 | 
			
		||||
 | 
			
		||||
type withCSSWriter struct {
 | 
			
		||||
	value io.Writer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withCSSWriter) SetConfig(c *renderer.Config) {
 | 
			
		||||
	c.Options[optCSSWriter] = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withCSSWriter) SetHighlightingOption(c *Config) {
 | 
			
		||||
	c.CSSWriter = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCSSWriter is a functional option that sets io.Writer for CSS data.
 | 
			
		||||
func WithCSSWriter(w io.Writer) Option {
 | 
			
		||||
	return &withCSSWriter{w}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optWrapperRenderer renderer.OptionName = "HighlightingWrapperRenderer"
 | 
			
		||||
 | 
			
		||||
type withWrapperRenderer struct {
 | 
			
		||||
	value WrapperRenderer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withWrapperRenderer) SetConfig(c *renderer.Config) {
 | 
			
		||||
	c.Options[optWrapperRenderer] = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withWrapperRenderer) SetHighlightingOption(c *Config) {
 | 
			
		||||
	c.WrapperRenderer = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithWrapperRenderer is a functional option that sets WrapperRenderer that
 | 
			
		||||
// renders wrapper elements like div, pre, etc.
 | 
			
		||||
func WithWrapperRenderer(w WrapperRenderer) Option {
 | 
			
		||||
	return &withWrapperRenderer{w}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optCodeBlockOptions renderer.OptionName = "HighlightingCodeBlockOptions"
 | 
			
		||||
 | 
			
		||||
type withCodeBlockOptions struct {
 | 
			
		||||
	value CodeBlockOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withCodeBlockOptions) SetConfig(c *renderer.Config) {
 | 
			
		||||
	c.Options[optWrapperRenderer] = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withCodeBlockOptions) SetHighlightingOption(c *Config) {
 | 
			
		||||
	c.CodeBlockOptions = o.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCodeBlockOptions is a functional option that sets CodeBlockOptions that
 | 
			
		||||
// allows setting Chroma options per code block.
 | 
			
		||||
func WithCodeBlockOptions(c CodeBlockOptions) Option {
 | 
			
		||||
	return &withCodeBlockOptions{value: c}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const optFormatOptions renderer.OptionName = "HighlightingFormatOptions"
 | 
			
		||||
 | 
			
		||||
type withFormatOptions struct {
 | 
			
		||||
	value []chromahtml.Option
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withFormatOptions) SetConfig(c *renderer.Config) {
 | 
			
		||||
	if _, ok := c.Options[optFormatOptions]; !ok {
 | 
			
		||||
		c.Options[optFormatOptions] = []chromahtml.Option{}
 | 
			
		||||
	}
 | 
			
		||||
	c.Options[optStyle] = append(c.Options[optFormatOptions].([]chromahtml.Option), o.value...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *withFormatOptions) SetHighlightingOption(c *Config) {
 | 
			
		||||
	c.FormatOptions = append(c.FormatOptions, o.value...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithFormatOptions is a functional option that wraps chroma HTML formatter options.
 | 
			
		||||
func WithFormatOptions(opts ...chromahtml.Option) Option {
 | 
			
		||||
	return &withFormatOptions{opts}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTMLRenderer struct is a renderer.NodeRenderer implementation for the extension.
 | 
			
		||||
type HTMLRenderer struct {
 | 
			
		||||
	Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewHTMLRenderer builds a new HTMLRenderer with given options and returns it.
 | 
			
		||||
func NewHTMLRenderer(opts ...Option) renderer.NodeRenderer {
 | 
			
		||||
	r := &HTMLRenderer{
 | 
			
		||||
		Config: NewConfig(),
 | 
			
		||||
	}
 | 
			
		||||
	for _, opt := range opts {
 | 
			
		||||
		opt.SetHighlightingOption(&r.Config)
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegisterFuncs implements NodeRenderer.RegisterFuncs.
 | 
			
		||||
func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 | 
			
		||||
	reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAttributes(node *ast.FencedCodeBlock, infostr []byte) ImmutableAttributes {
 | 
			
		||||
	if node.Attributes() != nil {
 | 
			
		||||
		return &immutableAttributes{node}
 | 
			
		||||
	}
 | 
			
		||||
	if infostr != nil {
 | 
			
		||||
		attrStartIdx := -1
 | 
			
		||||
 | 
			
		||||
		for idx, char := range infostr {
 | 
			
		||||
			if char == '{' {
 | 
			
		||||
				attrStartIdx = idx
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if attrStartIdx > 0 {
 | 
			
		||||
			n := ast.NewTextBlock() // dummy node for storing attributes
 | 
			
		||||
			attrStr := infostr[attrStartIdx:]
 | 
			
		||||
			if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
 | 
			
		||||
				for _, attr := range attrs {
 | 
			
		||||
					n.SetAttribute(attr.Name, attr.Value)
 | 
			
		||||
				}
 | 
			
		||||
				return &immutableAttributes{n}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
 | 
			
		||||
	n := node.(*ast.FencedCodeBlock)
 | 
			
		||||
	if !entering {
 | 
			
		||||
		return ast.WalkContinue, nil
 | 
			
		||||
	}
 | 
			
		||||
	language := n.Language(source)
 | 
			
		||||
 | 
			
		||||
	chromaFormatterOptions := make([]chromahtml.Option, len(r.FormatOptions))
 | 
			
		||||
	copy(chromaFormatterOptions, r.FormatOptions)
 | 
			
		||||
	style := styles.Get(r.Style)
 | 
			
		||||
	nohl := false
 | 
			
		||||
 | 
			
		||||
	var info []byte
 | 
			
		||||
	if n.Info != nil {
 | 
			
		||||
		info = n.Info.Segment.Value(source)
 | 
			
		||||
	}
 | 
			
		||||
	attrs := getAttributes(n, info)
 | 
			
		||||
	if attrs != nil {
 | 
			
		||||
		baseLineNumber := 1
 | 
			
		||||
		if linenostartAttr, ok := attrs.Get(linenostartAttrName); ok {
 | 
			
		||||
			baseLineNumber = int(linenostartAttr.(float64))
 | 
			
		||||
			chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.BaseLineNumber(baseLineNumber))
 | 
			
		||||
		}
 | 
			
		||||
		if linesAttr, hasLinesAttr := attrs.Get(highlightLinesAttrName); hasLinesAttr {
 | 
			
		||||
			if lines, ok := linesAttr.([]interface{}); ok {
 | 
			
		||||
				var hlRanges [][2]int
 | 
			
		||||
				for _, l := range lines {
 | 
			
		||||
					if ln, ok := l.(float64); ok {
 | 
			
		||||
						hlRanges = append(hlRanges, [2]int{int(ln) + baseLineNumber - 1, int(ln) + baseLineNumber - 1})
 | 
			
		||||
					}
 | 
			
		||||
					if rng, ok := l.([]uint8); ok {
 | 
			
		||||
						slices := strings.Split(string([]byte(rng)), "-")
 | 
			
		||||
						lhs, err := strconv.Atoi(slices[0])
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
						rhs := lhs
 | 
			
		||||
						if len(slices) > 1 {
 | 
			
		||||
							rhs, err = strconv.Atoi(slices[1])
 | 
			
		||||
							if err != nil {
 | 
			
		||||
								continue
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						hlRanges = append(hlRanges, [2]int{lhs + baseLineNumber - 1, rhs + baseLineNumber - 1})
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.HighlightLines(hlRanges))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if styleAttr, hasStyleAttr := attrs.Get(styleAttrName); hasStyleAttr {
 | 
			
		||||
			styleStr := string([]byte(styleAttr.([]uint8)))
 | 
			
		||||
			style = styles.Get(styleStr)
 | 
			
		||||
		}
 | 
			
		||||
		if _, hasNohlAttr := attrs.Get(nohlAttrName); hasNohlAttr {
 | 
			
		||||
			nohl = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if linenosAttr, ok := attrs.Get(linenosAttrName); ok {
 | 
			
		||||
			switch v := linenosAttr.(type) {
 | 
			
		||||
			case bool:
 | 
			
		||||
				chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.WithLineNumbers(v))
 | 
			
		||||
			case []uint8:
 | 
			
		||||
				if v != nil {
 | 
			
		||||
					chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.WithLineNumbers(true))
 | 
			
		||||
				}
 | 
			
		||||
				if bytes.Equal(v, linenosTableAttrValue) {
 | 
			
		||||
					chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.LineNumbersInTable(true))
 | 
			
		||||
				} else if bytes.Equal(v, linenosInlineAttrValue) {
 | 
			
		||||
					chromaFormatterOptions = append(chromaFormatterOptions, chromahtml.LineNumbersInTable(false))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var lexer chroma.Lexer
 | 
			
		||||
	if language != nil {
 | 
			
		||||
		lexer = lexers.Get(string(language))
 | 
			
		||||
	}
 | 
			
		||||
	if !nohl && lexer != nil {
 | 
			
		||||
		if style == nil {
 | 
			
		||||
			style = styles.Fallback
 | 
			
		||||
		}
 | 
			
		||||
		var buffer bytes.Buffer
 | 
			
		||||
		l := n.Lines().Len()
 | 
			
		||||
		for i := 0; i < l; i++ {
 | 
			
		||||
			line := n.Lines().At(i)
 | 
			
		||||
			buffer.Write(line.Value(source))
 | 
			
		||||
		}
 | 
			
		||||
		iterator, err := lexer.Tokenise(nil, buffer.String())
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			c := newCodeBlockContext(language, true, attrs)
 | 
			
		||||
 | 
			
		||||
			if r.CodeBlockOptions != nil {
 | 
			
		||||
				chromaFormatterOptions = append(chromaFormatterOptions, r.CodeBlockOptions(c)...)
 | 
			
		||||
			}
 | 
			
		||||
			formatter := chromahtml.New(chromaFormatterOptions...)
 | 
			
		||||
			if r.WrapperRenderer != nil {
 | 
			
		||||
				r.WrapperRenderer(w, c, true)
 | 
			
		||||
			}
 | 
			
		||||
			_ = formatter.Format(w, style, iterator) == nil
 | 
			
		||||
			if r.WrapperRenderer != nil {
 | 
			
		||||
				r.WrapperRenderer(w, c, false)
 | 
			
		||||
			}
 | 
			
		||||
			if r.CSSWriter != nil {
 | 
			
		||||
				_ = formatter.WriteCSS(r.CSSWriter, style)
 | 
			
		||||
			}
 | 
			
		||||
			return ast.WalkContinue, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var c CodeBlockContext
 | 
			
		||||
	if r.WrapperRenderer != nil {
 | 
			
		||||
		c = newCodeBlockContext(language, false, attrs)
 | 
			
		||||
		r.WrapperRenderer(w, c, true)
 | 
			
		||||
	} else {
 | 
			
		||||
		_, _ = w.WriteString("<pre><code")
 | 
			
		||||
		language := n.Language(source)
 | 
			
		||||
		if language != nil {
 | 
			
		||||
			_, _ = w.WriteString(" class=\"language-")
 | 
			
		||||
			r.Writer.Write(w, language)
 | 
			
		||||
			_, _ = w.WriteString("\"")
 | 
			
		||||
		}
 | 
			
		||||
		_ = w.WriteByte('>')
 | 
			
		||||
	}
 | 
			
		||||
	l := n.Lines().Len()
 | 
			
		||||
	for i := 0; i < l; i++ {
 | 
			
		||||
		line := n.Lines().At(i)
 | 
			
		||||
		r.Writer.RawWrite(w, line.Value(source))
 | 
			
		||||
	}
 | 
			
		||||
	if r.WrapperRenderer != nil {
 | 
			
		||||
		r.WrapperRenderer(w, c, false)
 | 
			
		||||
	} else {
 | 
			
		||||
		_, _ = w.WriteString("</code></pre>\n")
 | 
			
		||||
	}
 | 
			
		||||
	return ast.WalkContinue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type highlighting struct {
 | 
			
		||||
	options []Option
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Highlighting is a goldmark.Extender implementation.
 | 
			
		||||
var Highlighting = &highlighting{
 | 
			
		||||
	options: []Option{},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewHighlighting returns a new extension with given options.
 | 
			
		||||
func NewHighlighting(opts ...Option) goldmark.Extender {
 | 
			
		||||
	return &highlighting{
 | 
			
		||||
		options: opts,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extend implements goldmark.Extender.
 | 
			
		||||
func (e *highlighting) Extend(m goldmark.Markdown) {
 | 
			
		||||
	m.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
			
		||||
		util.Prioritized(NewHTMLRenderer(e.options...), 200),
 | 
			
		||||
	))
 | 
			
		||||
}
 | 
			
		||||
@@ -1,308 +0,0 @@
 | 
			
		||||
// TODO(bep) this is a very temporary fork based on https://github.com/yuin/goldmark-highlighting/pull/10
 | 
			
		||||
// MIT Licensed, Copyright Yusuke Inuzuka
 | 
			
		||||
package temphighlighting
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	chromahtml "github.com/alecthomas/chroma/formatters/html"
 | 
			
		||||
	"github.com/yuin/goldmark"
 | 
			
		||||
	"github.com/yuin/goldmark/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHighlighting(t *testing.T) {
 | 
			
		||||
	var css bytes.Buffer
 | 
			
		||||
	markdown := goldmark.New(
 | 
			
		||||
		goldmark.WithExtensions(
 | 
			
		||||
			NewHighlighting(
 | 
			
		||||
				WithStyle("monokai"),
 | 
			
		||||
				WithCSSWriter(&css),
 | 
			
		||||
				WithFormatOptions(
 | 
			
		||||
					chromahtml.WithClasses(true),
 | 
			
		||||
					chromahtml.WithLineNumbers(false),
 | 
			
		||||
				),
 | 
			
		||||
				WithWrapperRenderer(func(w util.BufWriter, c CodeBlockContext, entering bool) {
 | 
			
		||||
					_, ok := c.Language()
 | 
			
		||||
					if entering {
 | 
			
		||||
						if !ok {
 | 
			
		||||
							w.WriteString("<pre><code>")
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						w.WriteString(`<div class="highlight">`)
 | 
			
		||||
					} else {
 | 
			
		||||
						if !ok {
 | 
			
		||||
							w.WriteString("</pre></code>")
 | 
			
		||||
							return
 | 
			
		||||
						}
 | 
			
		||||
						w.WriteString(`</div>`)
 | 
			
		||||
					}
 | 
			
		||||
				}),
 | 
			
		||||
				WithCodeBlockOptions(func(c CodeBlockContext) []chromahtml.Option {
 | 
			
		||||
					if language, ok := c.Language(); ok {
 | 
			
		||||
						// Turn on line numbers for Go only.
 | 
			
		||||
						if string(language) == "go" {
 | 
			
		||||
							return []chromahtml.Option{
 | 
			
		||||
								chromahtml.WithLineNumbers(true),
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					return nil
 | 
			
		||||
				}),
 | 
			
		||||
			),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	if err := markdown.Convert([]byte(`
 | 
			
		||||
Title
 | 
			
		||||
=======
 | 
			
		||||
`+"``` go\n"+`func main() {
 | 
			
		||||
    fmt.Println("ok")
 | 
			
		||||
}
 | 
			
		||||
`+"```"+`
 | 
			
		||||
`), &buffer); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.TrimSpace(buffer.String()) != strings.TrimSpace(`
 | 
			
		||||
<h1>Title</h1>
 | 
			
		||||
<div class="highlight"><pre class="chroma"><span class="ln">1</span><span class="kd">func</span> <span class="nf">main</span><span class="p">(</span><span class="p">)</span> <span class="p">{</span>
 | 
			
		||||
<span class="ln">2</span>    <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"ok"</span><span class="p">)</span>
 | 
			
		||||
<span class="ln">3</span><span class="p">}</span>
 | 
			
		||||
</pre></div>
 | 
			
		||||
`) {
 | 
			
		||||
		t.Error("failed to render HTML")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.TrimSpace(css.String()) != strings.TrimSpace(`/* Background */ .chroma { color: #f8f8f2; background-color: #272822 }
 | 
			
		||||
/* Error */ .chroma .err { color: #960050; background-color: #1e0010 }
 | 
			
		||||
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
 | 
			
		||||
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
 | 
			
		||||
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #3c3d38 }
 | 
			
		||||
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
 | 
			
		||||
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
 | 
			
		||||
/* Keyword */ .chroma .k { color: #66d9ef }
 | 
			
		||||
/* KeywordConstant */ .chroma .kc { color: #66d9ef }
 | 
			
		||||
/* KeywordDeclaration */ .chroma .kd { color: #66d9ef }
 | 
			
		||||
/* KeywordNamespace */ .chroma .kn { color: #f92672 }
 | 
			
		||||
/* KeywordPseudo */ .chroma .kp { color: #66d9ef }
 | 
			
		||||
/* KeywordReserved */ .chroma .kr { color: #66d9ef }
 | 
			
		||||
/* KeywordType */ .chroma .kt { color: #66d9ef }
 | 
			
		||||
/* NameAttribute */ .chroma .na { color: #a6e22e }
 | 
			
		||||
/* NameClass */ .chroma .nc { color: #a6e22e }
 | 
			
		||||
/* NameConstant */ .chroma .no { color: #66d9ef }
 | 
			
		||||
/* NameDecorator */ .chroma .nd { color: #a6e22e }
 | 
			
		||||
/* NameException */ .chroma .ne { color: #a6e22e }
 | 
			
		||||
/* NameFunction */ .chroma .nf { color: #a6e22e }
 | 
			
		||||
/* NameOther */ .chroma .nx { color: #a6e22e }
 | 
			
		||||
/* NameTag */ .chroma .nt { color: #f92672 }
 | 
			
		||||
/* Literal */ .chroma .l { color: #ae81ff }
 | 
			
		||||
/* LiteralDate */ .chroma .ld { color: #e6db74 }
 | 
			
		||||
/* LiteralString */ .chroma .s { color: #e6db74 }
 | 
			
		||||
/* LiteralStringAffix */ .chroma .sa { color: #e6db74 }
 | 
			
		||||
/* LiteralStringBacktick */ .chroma .sb { color: #e6db74 }
 | 
			
		||||
/* LiteralStringChar */ .chroma .sc { color: #e6db74 }
 | 
			
		||||
/* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 }
 | 
			
		||||
/* LiteralStringDoc */ .chroma .sd { color: #e6db74 }
 | 
			
		||||
/* LiteralStringDouble */ .chroma .s2 { color: #e6db74 }
 | 
			
		||||
/* LiteralStringEscape */ .chroma .se { color: #ae81ff }
 | 
			
		||||
/* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 }
 | 
			
		||||
/* LiteralStringInterpol */ .chroma .si { color: #e6db74 }
 | 
			
		||||
/* LiteralStringOther */ .chroma .sx { color: #e6db74 }
 | 
			
		||||
/* LiteralStringRegex */ .chroma .sr { color: #e6db74 }
 | 
			
		||||
/* LiteralStringSingle */ .chroma .s1 { color: #e6db74 }
 | 
			
		||||
/* LiteralStringSymbol */ .chroma .ss { color: #e6db74 }
 | 
			
		||||
/* LiteralNumber */ .chroma .m { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberBin */ .chroma .mb { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberFloat */ .chroma .mf { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberHex */ .chroma .mh { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberInteger */ .chroma .mi { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff }
 | 
			
		||||
/* LiteralNumberOct */ .chroma .mo { color: #ae81ff }
 | 
			
		||||
/* Operator */ .chroma .o { color: #f92672 }
 | 
			
		||||
/* OperatorWord */ .chroma .ow { color: #f92672 }
 | 
			
		||||
/* Comment */ .chroma .c { color: #75715e }
 | 
			
		||||
/* CommentHashbang */ .chroma .ch { color: #75715e }
 | 
			
		||||
/* CommentMultiline */ .chroma .cm { color: #75715e }
 | 
			
		||||
/* CommentSingle */ .chroma .c1 { color: #75715e }
 | 
			
		||||
/* CommentSpecial */ .chroma .cs { color: #75715e }
 | 
			
		||||
/* CommentPreproc */ .chroma .cp { color: #75715e }
 | 
			
		||||
/* CommentPreprocFile */ .chroma .cpf { color: #75715e }
 | 
			
		||||
/* GenericDeleted */ .chroma .gd { color: #f92672 }
 | 
			
		||||
/* GenericEmph */ .chroma .ge { font-style: italic }
 | 
			
		||||
/* GenericInserted */ .chroma .gi { color: #a6e22e }
 | 
			
		||||
/* GenericStrong */ .chroma .gs { font-weight: bold }
 | 
			
		||||
/* GenericSubheading */ .chroma .gu { color: #75715e }`) {
 | 
			
		||||
		t.Error("failed to render CSS")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHighlighting2(t *testing.T) {
 | 
			
		||||
	markdown := goldmark.New(
 | 
			
		||||
		goldmark.WithExtensions(
 | 
			
		||||
			Highlighting,
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	if err := markdown.Convert([]byte(`
 | 
			
		||||
Title
 | 
			
		||||
=======
 | 
			
		||||
`+"```"+`
 | 
			
		||||
func main() {
 | 
			
		||||
    fmt.Println("ok")
 | 
			
		||||
}
 | 
			
		||||
`+"```"+`
 | 
			
		||||
`), &buffer); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if strings.TrimSpace(buffer.String()) != strings.TrimSpace(`
 | 
			
		||||
<h1>Title</h1>
 | 
			
		||||
<pre><code>func main() {
 | 
			
		||||
    fmt.Println("ok")
 | 
			
		||||
}
 | 
			
		||||
</code></pre>
 | 
			
		||||
`) {
 | 
			
		||||
		t.Error("failed to render HTML")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHighlighting3(t *testing.T) {
 | 
			
		||||
	markdown := goldmark.New(
 | 
			
		||||
		goldmark.WithExtensions(
 | 
			
		||||
			Highlighting,
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
	var buffer bytes.Buffer
 | 
			
		||||
	if err := markdown.Convert([]byte(`
 | 
			
		||||
Title
 | 
			
		||||
=======
 | 
			
		||||
 | 
			
		||||
`+"```"+`cpp {hl_lines=[1,2]}
 | 
			
		||||
#include <iostream>
 | 
			
		||||
int main() {
 | 
			
		||||
    std::cout<< "hello" << std::endl;
 | 
			
		||||
}
 | 
			
		||||
`+"```"+`
 | 
			
		||||
`), &buffer); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if strings.TrimSpace(buffer.String()) != strings.TrimSpace(`
 | 
			
		||||
<h1>Title</h1>
 | 
			
		||||
<pre style="background-color:#fff"><span style="display:block;width:100%;background-color:#e5e5e5"><span style="color:#999;font-weight:bold;font-style:italic">#</span><span style="color:#999;font-weight:bold;font-style:italic">include</span> <span style="color:#999;font-weight:bold;font-style:italic"><iostream></span><span style="color:#999;font-weight:bold;font-style:italic">
 | 
			
		||||
</span></span><span style="display:block;width:100%;background-color:#e5e5e5"><span style="color:#999;font-weight:bold;font-style:italic"></span><span style="color:#458;font-weight:bold">int</span> <span style="color:#900;font-weight:bold">main</span>() {
 | 
			
		||||
</span>    std<span style="color:#000;font-weight:bold">:</span><span style="color:#000;font-weight:bold">:</span>cout<span style="color:#000;font-weight:bold"><</span><span style="color:#000;font-weight:bold"><</span> <span style="color:#d14"></span><span style="color:#d14">"</span><span style="color:#d14">hello</span><span style="color:#d14">"</span> <span style="color:#000;font-weight:bold"><</span><span style="color:#000;font-weight:bold"><</span> std<span style="color:#000;font-weight:bold">:</span><span style="color:#000;font-weight:bold">:</span>endl;
 | 
			
		||||
}
 | 
			
		||||
</pre>
 | 
			
		||||
`) {
 | 
			
		||||
		t.Error("failed to render HTML")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHighlightingHlLines(t *testing.T) {
 | 
			
		||||
	markdown := goldmark.New(
 | 
			
		||||
		goldmark.WithExtensions(
 | 
			
		||||
			NewHighlighting(
 | 
			
		||||
				WithFormatOptions(
 | 
			
		||||
					chromahtml.WithClasses(true),
 | 
			
		||||
				),
 | 
			
		||||
			),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for i, test := range []struct {
 | 
			
		||||
		attributes string
 | 
			
		||||
		expect     []int
 | 
			
		||||
	}{
 | 
			
		||||
		{`hl_lines=["2"]`, []int{2}},
 | 
			
		||||
		{`hl_lines=["2-3",5],linenostart=5`, []int{2, 3, 5}},
 | 
			
		||||
		{`hl_lines=["2-3"]`, []int{2, 3}},
 | 
			
		||||
	} {
 | 
			
		||||
		t.Run(fmt.Sprint(i), func(t *testing.T) {
 | 
			
		||||
			var buffer bytes.Buffer
 | 
			
		||||
			codeBlock := fmt.Sprintf(`bash {%s}
 | 
			
		||||
LINE1
 | 
			
		||||
LINE2
 | 
			
		||||
LINE3
 | 
			
		||||
LINE4
 | 
			
		||||
LINE5
 | 
			
		||||
LINE6
 | 
			
		||||
LINE7
 | 
			
		||||
LINE8
 | 
			
		||||
`, test.attributes)
 | 
			
		||||
 | 
			
		||||
			if err := markdown.Convert([]byte(`
 | 
			
		||||
`+"```"+codeBlock+"```"+`
 | 
			
		||||
`), &buffer); err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, line := range test.expect {
 | 
			
		||||
				expectStr := fmt.Sprintf("<span class=\"hl\">LINE%d\n</span>", line)
 | 
			
		||||
				if !strings.Contains(buffer.String(), expectStr) {
 | 
			
		||||
					t.Fatal("got\n", buffer.String(), "\nexpected\n", expectStr)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHighlightingLinenos(t *testing.T) {
 | 
			
		||||
	outputLineNumbersInTable := `<div class="chroma">
 | 
			
		||||
<table class="lntable"><tr><td class="lntd">
 | 
			
		||||
<span class="lnt">1
 | 
			
		||||
</span></td>
 | 
			
		||||
<td class="lntd">
 | 
			
		||||
LINE1
 | 
			
		||||
</td></tr></table>
 | 
			
		||||
</div>`
 | 
			
		||||
 | 
			
		||||
	for i, test := range []struct {
 | 
			
		||||
		attributes         string
 | 
			
		||||
		lineNumbers        bool
 | 
			
		||||
		lineNumbersInTable bool
 | 
			
		||||
		expect             string
 | 
			
		||||
	}{
 | 
			
		||||
		{`linenos=true`, false, false, `<span class="ln">1</span>LINE1`},
 | 
			
		||||
		{`linenos=false`, false, false, `LINE1`},
 | 
			
		||||
		{``, true, false, `<span class="ln">1</span>LINE1`},
 | 
			
		||||
		{``, true, true, outputLineNumbersInTable},
 | 
			
		||||
		{`linenos=inline`, true, true, `<span class="ln">1</span>LINE1`},
 | 
			
		||||
		{`linenos=foo`, false, false, `<span class="ln">1</span>LINE1`},
 | 
			
		||||
		{`linenos=table`, false, false, outputLineNumbersInTable},
 | 
			
		||||
	} {
 | 
			
		||||
		t.Run(fmt.Sprint(i), func(t *testing.T) {
 | 
			
		||||
			markdown := goldmark.New(
 | 
			
		||||
				goldmark.WithExtensions(
 | 
			
		||||
					NewHighlighting(
 | 
			
		||||
						WithFormatOptions(
 | 
			
		||||
							chromahtml.WithLineNumbers(test.lineNumbers),
 | 
			
		||||
							chromahtml.LineNumbersInTable(test.lineNumbersInTable),
 | 
			
		||||
							chromahtml.PreventSurroundingPre(true),
 | 
			
		||||
							chromahtml.WithClasses(true),
 | 
			
		||||
						),
 | 
			
		||||
					),
 | 
			
		||||
				),
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			var buffer bytes.Buffer
 | 
			
		||||
			codeBlock := fmt.Sprintf(`bash {%s}
 | 
			
		||||
LINE1
 | 
			
		||||
`, test.attributes)
 | 
			
		||||
 | 
			
		||||
			content := "```" + codeBlock + "```"
 | 
			
		||||
 | 
			
		||||
			if err := markdown.Convert([]byte(content), &buffer); err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			s := strings.TrimSpace(buffer.String())
 | 
			
		||||
 | 
			
		||||
			if s != test.expect {
 | 
			
		||||
				t.Fatal("got\n", s, "\nexpected\n", test.expect)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user