diff --git a/gno.land/pkg/gnoweb/app.go b/gno.land/pkg/gnoweb/app.go index dc13253468e..1deea86644b 100644 --- a/gno.land/pkg/gnoweb/app.go +++ b/gno.land/pkg/gnoweb/app.go @@ -7,11 +7,12 @@ import ( "path" "strings" + markdown "github.com/yuin/goldmark-highlighting/v2" + "github.com/alecthomas/chroma/v2" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/v2/styles" "github.com/gnolang/gno/gno.land/pkg/gnoweb/components" - "github.com/gnolang/gno/gno.land/pkg/gnoweb/markdown" "github.com/gnolang/gno/tm2/pkg/bft/rpc/client" "github.com/yuin/goldmark" mdhtml "github.com/yuin/goldmark/renderer/html" diff --git a/gno.land/pkg/gnoweb/markdown/highlighting.go b/gno.land/pkg/gnoweb/markdown/highlighting.go deleted file mode 100644 index 51c66674df1..00000000000 --- a/gno.land/pkg/gnoweb/markdown/highlighting.go +++ /dev/null @@ -1,588 +0,0 @@ -// This file was copied from https://github.com/yuin/goldmark-highlighting -// -// package highlighting is an 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). -package markdown - -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/v2" - chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/lexers" - "github.com/alecthomas/chroma/v2/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 - - // Pass in a custom Chroma style. If this is not nil, the Style string will be ignored - CustomStyle *chroma.Style - - // If set, will try to guess language if none provided. - // If the guessing fails, we will fall back to a text lexer. - // Note that while Chroma's API supports language guessing, the implementation - // is not there yet, so you will currently always get the basic text lexer. - GuessLanguage bool - - // 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 - - // WrapperRenderer 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 optCustomStyle: - c.CustomStyle = value.(*chroma.Style) - 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) - case optGuessLanguage: - c.GuessLanguage = value.(bool) - 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" - optCustomStyle renderer.OptionName = "HighlightingCustomStyle" -) - -var highlightLinesAttrName = []byte("hl_lines") - -var ( - styleAttrName = []byte("hl_style") - nohlAttrName = []byte("nohl") - linenosAttrName = []byte("linenos") - linenosTableAttrValue = []byte("table") - linenosInlineAttrValue = []byte("inline") - 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} -} - -type withCustomStyle struct { - value *chroma.Style -} - -func (o *withCustomStyle) SetConfig(c *renderer.Config) { - c.Options[optCustomStyle] = o.value -} - -func (o *withCustomStyle) SetHighlightingOption(c *Config) { - c.CustomStyle = o.value -} - -// WithStyle is a functional option that changes highlighting style. -func WithCustomStyle(style *chroma.Style) Option { - return &withCustomStyle{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 optGuessLanguage renderer.OptionName = "HighlightingGuessLanguage" - -type withGuessLanguage struct { - value bool -} - -func (o *withGuessLanguage) SetConfig(c *renderer.Config) { - c.Options[optGuessLanguage] = o.value -} - -func (o *withGuessLanguage) SetHighlightingOption(c *Config) { - c.GuessLanguage = o.value -} - -// WithGuessLanguage is a functional option that toggles language guessing -// if none provided. -func WithGuessLanguage(b bool) Option { - return &withGuessLanguage{value: b} -} - -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[optCodeBlockOptions] = 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[optFormatOptions] = 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, 0, len(r.FormatOptions)) - for _, opt := range r.FormatOptions { - chromaFormatterOptions = append(chromaFormatterOptions, opt) - } - - style := r.CustomStyle - if style == nil { - 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 { - if linenostart, ok := linenostartAttr.(float64); ok { - baseLineNumber = int(linenostart) - 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(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 { - if st, ok := styleAttr.([]uint8); ok { - styleStr := string(st) - 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 || r.GuessLanguage) { - 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)) - } - - if lexer == nil { - lexer = lexers.Analyse(buffer.String()) - if lexer == nil { - lexer = lexers.Fallback - } - language = []byte(strings.ToLower(lexer.Config().Name)) - } - lexer = chroma.Coalesce(lexer) - - 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/gno.land/pkg/gnoweb/markdown/highlighting_test.go b/gno.land/pkg/gnoweb/markdown/highlighting_test.go
deleted file mode 100644
index 25bc4fedd61..00000000000
--- a/gno.land/pkg/gnoweb/markdown/highlighting_test.go
+++ /dev/null
@@ -1,568 +0,0 @@
-// This file was copied from https://github.com/yuin/goldmark-highlighting
-
-package markdown
-
-import (
- "bytes"
- "fmt"
- "strings"
- "testing"
-
- "github.com/alecthomas/chroma/v2"
- chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/testutil"
- "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.Errorf("failed to render HTML\n%s", buffer.String())
- }
-
- expected := strings.TrimSpace(`/* Background */ .bg { color: #f8f8f2; background-color: #272822; }
-/* PreWrapper */ .chroma { color: #f8f8f2; background-color: #272822; }
-/* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #f8f8f2; background-color: #3c3d38 }
-/* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #f8f8f2; background-color: #3c3d38 }
-/* Error */ .chroma .err { color: #960050; background-color: #1e0010 }
-/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
-/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
-/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
-/* LineHighlight */ .chroma .hl { background-color: #3c3d38 }
-/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
-/* Line */ .chroma .line { display: flex; }
-/* 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 }`)
-
- gotten := strings.TrimSpace(css.String())
-
- if expected != gotten {
- diff := testutil.DiffPretty([]byte(expected), []byte(gotten))
- t.Errorf("incorrect CSS.\n%s", string(diff))
- }
-}
-
-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.Errorf("failed to render HTML:\n%s", buffer.String())
- }
-}
-
-func TestHighlightingCustom(t *testing.T) {
- custom := chroma.MustNewStyle("custom", chroma.StyleEntries{
- chroma.Background: "#cccccc bg:#1d1d1d",
- chroma.Comment: "#999999",
- chroma.CommentSpecial: "#cd0000",
- chroma.Keyword: "#cc99cd",
- chroma.KeywordDeclaration: "#cc99cd",
- chroma.KeywordNamespace: "#cc99cd",
- chroma.KeywordType: "#cc99cd",
- chroma.Operator: "#67cdcc",
- chroma.OperatorWord: "#cdcd00",
- chroma.NameClass: "#f08d49",
- chroma.NameBuiltin: "#f08d49",
- chroma.NameFunction: "#f08d49",
- chroma.NameException: "bold #666699",
- chroma.NameVariable: "#00cdcd",
- chroma.LiteralString: "#7ec699",
- chroma.LiteralNumber: "#f08d49",
- chroma.LiteralStringBoolean: "#f08d49",
- chroma.GenericHeading: "bold #000080",
- chroma.GenericSubheading: "bold #800080",
- chroma.GenericDeleted: "#e2777a",
- chroma.GenericInserted: "#cc99cd",
- chroma.GenericError: "#e2777a",
- chroma.GenericEmph: "italic",
- chroma.GenericStrong: "bold",
- chroma.GenericPrompt: "bold #000080",
- chroma.GenericOutput: "#888",
- chroma.GenericTraceback: "#04D",
- chroma.GenericUnderline: "underline",
- chroma.Error: "border:#e2777a",
- })
-
- var css bytes.Buffer
- markdown := goldmark.New(
- goldmark.WithExtensions(
- NewHighlighting(
- WithStyle("monokai"), // to make sure it is overrided even if present
- WithCustomStyle(custom),
- 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", buffer.String())
- }
-
- expected := strings.TrimSpace(`/* Background */ .bg { color: #cccccc; background-color: #1d1d1d; }
-/* PreWrapper */ .chroma { color: #cccccc; background-color: #1d1d1d; }
-/* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #cccccc; background-color: #333333 }
-/* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #cccccc; background-color: #333333 }
-/* Error */ .chroma .err { }
-/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
-/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
-/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
-/* LineHighlight */ .chroma .hl { background-color: #333333 }
-/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 }
-/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 }
-/* Line */ .chroma .line { display: flex; }
-/* Keyword */ .chroma .k { color: #cc99cd }
-/* KeywordConstant */ .chroma .kc { color: #cc99cd }
-/* KeywordDeclaration */ .chroma .kd { color: #cc99cd }
-/* KeywordNamespace */ .chroma .kn { color: #cc99cd }
-/* KeywordPseudo */ .chroma .kp { color: #cc99cd }
-/* KeywordReserved */ .chroma .kr { color: #cc99cd }
-/* KeywordType */ .chroma .kt { color: #cc99cd }
-/* NameBuiltin */ .chroma .nb { color: #f08d49 }
-/* NameClass */ .chroma .nc { color: #f08d49 }
-/* NameException */ .chroma .ne { color: #666699; font-weight: bold }
-/* NameFunction */ .chroma .nf { color: #f08d49 }
-/* NameVariable */ .chroma .nv { color: #00cdcd }
-/* LiteralString */ .chroma .s { color: #7ec699 }
-/* LiteralStringAffix */ .chroma .sa { color: #7ec699 }
-/* LiteralStringBacktick */ .chroma .sb { color: #7ec699 }
-/* LiteralStringChar */ .chroma .sc { color: #7ec699 }
-/* LiteralStringDelimiter */ .chroma .dl { color: #7ec699 }
-/* LiteralStringDoc */ .chroma .sd { color: #7ec699 }
-/* LiteralStringDouble */ .chroma .s2 { color: #7ec699 }
-/* LiteralStringEscape */ .chroma .se { color: #7ec699 }
-/* LiteralStringHeredoc */ .chroma .sh { color: #7ec699 }
-/* LiteralStringInterpol */ .chroma .si { color: #7ec699 }
-/* LiteralStringOther */ .chroma .sx { color: #7ec699 }
-/* LiteralStringRegex */ .chroma .sr { color: #7ec699 }
-/* LiteralStringSingle */ .chroma .s1 { color: #7ec699 }
-/* LiteralStringSymbol */ .chroma .ss { color: #7ec699 }
-/* LiteralNumber */ .chroma .m { color: #f08d49 }
-/* LiteralNumberBin */ .chroma .mb { color: #f08d49 }
-/* LiteralNumberFloat */ .chroma .mf { color: #f08d49 }
-/* LiteralNumberHex */ .chroma .mh { color: #f08d49 }
-/* LiteralNumberInteger */ .chroma .mi { color: #f08d49 }
-/* LiteralNumberIntegerLong */ .chroma .il { color: #f08d49 }
-/* LiteralNumberOct */ .chroma .mo { color: #f08d49 }
-/* Operator */ .chroma .o { color: #67cdcc }
-/* OperatorWord */ .chroma .ow { color: #cdcd00 }
-/* Comment */ .chroma .c { color: #999999 }
-/* CommentHashbang */ .chroma .ch { color: #999999 }
-/* CommentMultiline */ .chroma .cm { color: #999999 }
-/* CommentSingle */ .chroma .c1 { color: #999999 }
-/* CommentSpecial */ .chroma .cs { color: #cd0000 }
-/* CommentPreproc */ .chroma .cp { color: #999999 }
-/* CommentPreprocFile */ .chroma .cpf { color: #999999 }
-/* GenericDeleted */ .chroma .gd { color: #e2777a }
-/* GenericEmph */ .chroma .ge { font-style: italic }
-/* GenericError */ .chroma .gr { color: #e2777a }
-/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
-/* GenericInserted */ .chroma .gi { color: #cc99cd }
-/* GenericOutput */ .chroma .go { color: #888888 }
-/* GenericPrompt */ .chroma .gp { color: #000080; font-weight: bold }
-/* GenericStrong */ .chroma .gs { font-weight: bold }
-/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
-/* GenericTraceback */ .chroma .gt { color: #0044dd }
-/* GenericUnderline */ .chroma .gl { text-decoration: underline }`)
-
- gotten := strings.TrimSpace(css.String())
-
- if expected != gotten {
- diff := testutil.DiffPretty([]byte(expected), []byte(gotten))
- t.Errorf("incorrect CSS.\n%s", string(diff))
- }
-}
-
-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}},
- {`hl_lines=["2-3",5],linenostart="5"`, []int{2, 3}}, // linenostart must be a number. string values are ignored
- } {
- 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)
- }
- }
- })
- }
-}
-
-type nopPreWrapper struct{}
-
-// Start is called to write a start element.
-func (nopPreWrapper) Start(code bool, styleAttr string) string { return "" }
-
-// End is called to write the end
element.
-func (nopPreWrapper) End(code bool) string { return "" }
-
-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.WithPreWrapper(nopPreWrapper{}),
- 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)
- }
- })
- }
-}
-
-func TestHighlightingGuessLanguage(t *testing.T) {
- markdown := goldmark.New(
- goldmark.WithExtensions(
- NewHighlighting(
- WithGuessLanguage(true),
- WithFormatOptions(
- chromahtml.WithClasses(true),
- chromahtml.WithLineNumbers(true),
- ),
- ),
- ),
- )
- var buffer bytes.Buffer
- if err := markdown.Convert([]byte("```"+`
-LINE
-`+"```"), &buffer); err != nil {
- t.Fatal(err)
- }
- if strings.TrimSpace(buffer.String()) != strings.TrimSpace(`
-1LINE
-
-`) {
- t.Errorf("render mismatch, got\n%s", buffer.String())
- }
-}
-
-func TestCoalesceNeeded(t *testing.T) {
- markdown := goldmark.New(
- goldmark.WithExtensions(
- NewHighlighting(
- // WithGuessLanguage(true),
- WithFormatOptions(
- chromahtml.WithClasses(true),
- chromahtml.WithLineNumbers(true),
- ),
- ),
- ),
- )
- var buffer bytes.Buffer
- if err := markdown.Convert([]byte("```http"+`
-GET /foo HTTP/1.1
-Content-Type: application/json
-User-Agent: foo
-
-{
- "hello": "world"
-}
-`+"```"), &buffer); err != nil {
- t.Fatal(err)
- }
- if strings.TrimSpace(buffer.String()) != strings.TrimSpace(`
-1GET /foo HTTP/1.1
-2Content-Type: application/json
-3User-Agent: foo
-4
-5{
-6 "hello": "world"
-7}
-
-`) {
- t.Errorf("render mismatch, got\n%s", buffer.String())
- }
-}
diff --git a/go.mod b/go.mod
index ed7c3b75528..280ca3ae602 100644
--- a/go.mod
+++ b/go.mod
@@ -27,6 +27,7 @@ require (
github.com/stretchr/testify v1.9.0
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/yuin/goldmark v1.7.2
+ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
go.etcd.io/bbolt v1.3.11
go.opentelemetry.io/otel v1.29.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.29.0
diff --git a/go.sum b/go.sum
index a4ccfbbdd66..9c4d20dbad6 100644
--- a/go.sum
+++ b/go.sum
@@ -3,8 +3,10 @@ dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
+github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
+github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
@@ -51,6 +53,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeC
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
+github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -144,8 +148,11 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.2 h1:NjGd7lO7zrUn/A7eKwn5PEOt4ONYGqpxSEeZuduvgxc=
github.com/yuin/goldmark v1.7.2/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
+github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U=
github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=