mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
markup/highlight: Replace the temp for with a dependency
This commit is contained in:
parent
b546417a27
commit
a2d77f4a80
6 changed files with 5 additions and 822 deletions
1
go.mod
1
go.mod
|
@ -55,6 +55,7 @@ require (
|
||||||
github.com/tdewolff/minify/v2 v2.5.2
|
github.com/tdewolff/minify/v2 v2.5.2
|
||||||
github.com/yosssi/ace v0.0.5
|
github.com/yosssi/ace v0.0.5
|
||||||
github.com/yuin/goldmark v1.1.5
|
github.com/yuin/goldmark v1.1.5
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20191124122839-ede94e40cc3a
|
||||||
go.opencensus.io v0.22.0 // indirect
|
go.opencensus.io v0.22.0 // indirect
|
||||||
gocloud.dev v0.15.0
|
gocloud.dev v0.15.0
|
||||||
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff
|
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -353,6 +353,8 @@ github.com/yuin/goldmark v1.1.4 h1:Fj9vOhXMWRBITkIfa8OG/5j6PTKPkyPHxZbT1bvmjV8=
|
||||||
github.com/yuin/goldmark v1.1.4/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.4/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.5 h1:JJy3EDke+PMI2WcFIU6SdaeiP6FgRGK5NKAiPZHiOoE=
|
github.com/yuin/goldmark v1.1.5 h1:JJy3EDke+PMI2WcFIU6SdaeiP6FgRGK5NKAiPZHiOoE=
|
||||||
github.com/yuin/goldmark v1.1.5/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.5/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20191124122839-ede94e40cc3a h1:L7FTUnbc0WEBqGWgjbx4sPNAOX1/q5W/3KCD6g8XkKo=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20191124122839-ede94e40cc3a/go.mod h1:1gshkGdH4gcrIH5MGSScGH42rOOCO+4Ks6acjAkA9C0=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.mongodb.org/mongo-driver v1.0.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
go.mongodb.org/mongo-driver v1.0.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
|
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
|
||||||
|
|
|
@ -28,10 +28,10 @@ import (
|
||||||
"github.com/alecthomas/chroma/styles"
|
"github.com/alecthomas/chroma/styles"
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
"github.com/gohugoio/hugo/markup/highlight"
|
"github.com/gohugoio/hugo/markup/highlight"
|
||||||
hl "github.com/gohugoio/hugo/markup/highlight/temphighlighting"
|
|
||||||
"github.com/gohugoio/hugo/markup/markup_config"
|
"github.com/gohugoio/hugo/markup/markup_config"
|
||||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
hl "github.com/yuin/goldmark-highlighting"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer"
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/formatters/html"
|
||||||
"github.com/alecthomas/chroma/lexers"
|
"github.com/alecthomas/chroma/lexers"
|
||||||
"github.com/alecthomas/chroma/styles"
|
"github.com/alecthomas/chroma/styles"
|
||||||
hl "github.com/gohugoio/hugo/markup/highlight/temphighlighting"
|
hl "github.com/yuin/goldmark-highlighting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(cfg Config) Highlighter {
|
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue