diff --git a/go.mod b/go.mod index 2bfed0302..eaa6892bd 100644 --- a/go.mod +++ b/go.mod @@ -55,6 +55,7 @@ require ( github.com/tdewolff/minify/v2 v2.5.2 github.com/yosssi/ace v0.0.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 gocloud.dev v0.15.0 golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff diff --git a/go.sum b/go.sum index 866148569..33f8f353f 100644 --- a/go.sum +++ b/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.5 h1:JJy3EDke+PMI2WcFIU6SdaeiP6FgRGK5NKAiPZHiOoE= 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.mongodb.org/mongo-driver v1.0.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index 6c9a3771a..9ce0b0b56 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -28,10 +28,10 @@ import ( "github.com/alecthomas/chroma/styles" "github.com/gohugoio/hugo/markup/converter" "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/tableofcontents" "github.com/yuin/goldmark" + hl "github.com/yuin/goldmark-highlighting" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer" diff --git a/markup/highlight/highlight.go b/markup/highlight/highlight.go index cec637c72..322bde1ef 100644 --- a/markup/highlight/highlight.go +++ b/markup/highlight/highlight.go @@ -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 { diff --git a/markup/highlight/temphighlighting/highlighting.go b/markup/highlight/temphighlighting/highlighting.go deleted file mode 100644 index d2f16c506..000000000 --- a/markup/highlight/temphighlighting/highlighting.go +++ /dev/null @@ -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("
')
-	}
-	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("
\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), - )) -} diff --git a/markup/highlight/temphighlighting/highlighting_test.go b/markup/highlight/temphighlighting/highlighting_test.go deleted file mode 100644 index a2d605856..000000000 --- a/markup/highlight/temphighlighting/highlighting_test.go +++ /dev/null @@ -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("
")
-							return
-						}
-						w.WriteString(`
`) - } else { - if !ok { - w.WriteString("
") - return - } - w.WriteString(``) - } - }), - 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(` -

Title

-
1func main() {
-2    fmt.Println("ok")
-3}
-
-`) { - 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(` -

Title

-
func main() {
-    fmt.Println("ok")
-}
-
-`) { - 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 -int main() { - std::cout<< "hello" << std::endl; -} -`+"```"+` -`), &buffer); err != nil { - t.Fatal(err) - } - if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` -

Title

-
#include <iostream>
-int main() {
-    std::cout<< "hello" << std::endl;
-}
-
-`) { - 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("LINE%d\n", line) - if !strings.Contains(buffer.String(), expectStr) { - t.Fatal("got\n", buffer.String(), "\nexpected\n", expectStr) - } - } - }) - } -} - -func TestHighlightingLinenos(t *testing.T) { - outputLineNumbersInTable := `
- -
-1 - -LINE1 -
-
` - - for i, test := range []struct { - attributes string - lineNumbers bool - lineNumbersInTable bool - expect string - }{ - {`linenos=true`, false, false, `1LINE1`}, - {`linenos=false`, false, false, `LINE1`}, - {``, true, false, `1LINE1`}, - {``, true, true, outputLineNumbersInTable}, - {`linenos=inline`, true, true, `1LINE1`}, - {`linenos=foo`, false, false, `1LINE1`}, - {`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) - } - }) - } -}