diff --git a/doc.go b/doc.go index 57ff152a..2a5cc5b6 100644 --- a/doc.go +++ b/doc.go @@ -16,7 +16,7 @@ // If you're interested in calling Blackfriday from command line, see // https://github.com/russross/blackfriday-tool. // -// Sanitized Anchor Names +// # Sanitized Anchor Names // // Blackfriday includes an algorithm for creating sanitized anchor names // corresponding to a given input text. This algorithm is used to create diff --git a/go.mod b/go.mod index 620b74e0..8d717a94 100644 --- a/go.mod +++ b/go.mod @@ -1 +1,7 @@ module github.com/russross/blackfriday/v2 + +go 1.17 + +require github.com/alecthomas/chroma v0.9.4 + +require github.com/dlclark/regexp2 v1.4.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..70739ce2 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/alecthomas/chroma v0.9.4 h1:YL7sOAE3p8HS96T9km7RgvmsZIctqbK1qJ0b7hzed44= +github.com/alecthomas/chroma v0.9.4/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/html.go b/html.go index cb4f26e3..d50666bd 100644 --- a/html.go +++ b/html.go @@ -21,6 +21,11 @@ import ( "io" "regexp" "strings" + + "github.com/alecthomas/chroma" + "github.com/alecthomas/chroma/formatters/html" + "github.com/alecthomas/chroma/lexers" + "github.com/alecthomas/chroma/styles" ) // HTMLFlags control optional behavior of HTML renderer. @@ -319,15 +324,15 @@ func isSmartypantable(node *Node) bool { return pt != Link && pt != CodeBlock && pt != Code } -func appendLanguageAttr(attrs []string, info []byte) []string { +func appendLanguageAttr(attrs []string, info []byte) ([]string, string) { if len(info) == 0 { - return attrs + return attrs, "" } endOfLang := bytes.IndexAny(info, "\t ") if endOfLang < 0 { endOfLang = len(info) } - return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) + return append(attrs, fmt.Sprintf("class=\"language-%s highlighter-rouge\"", info[:endOfLang])), string(info[:endOfLang]) } func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { @@ -420,6 +425,8 @@ var ( aTag = []byte("") preTag = []byte("
")
+	divTag             = []byte(`
`) + divCloseTag = []byte("
") preCloseTag = []byte("
") codeTag = []byte("") codeCloseTag = []byte("") @@ -761,13 +768,44 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.cr(w) } case CodeBlock: - attrs = appendLanguageAttr(attrs, node.Info) + attrs, lang := appendLanguageAttr(attrs, node.Info) r.cr(w) - r.out(w, preTag) - r.tag(w, codeTag[:len(codeTag)-1], attrs) - escapeAllHTML(w, node.Literal) - r.out(w, codeCloseTag) - r.out(w, preCloseTag) + r.tag(w, divTag[:len(divTag)-1], attrs) + r.tag(w, divTag[:len(divTag)-1], []string{`class="highlight"`}) + + source := string(node.Literal) + + // Determine lexer. + l := lexers.Get(lang) + if l == nil { + l = lexers.Analyse(source) + } + if l == nil { + l = lexers.Fallback + } + l = chroma.Coalesce(l) + + // Determine formatter. + f := html.New(html.WithClasses(true)) + + // Determine style. + s := styles.Get("") + if s == nil { + s = styles.Fallback + } + + it, err := l.Tokenise(nil, source) + if err != nil { + panic(err) + } + + if err = f.Format(w, s, it); err != nil { + panic(err) + } + + r.out(w, divCloseTag) + r.out(w, divCloseTag) + if node.Parent.Type != Item { r.cr(w) } diff --git a/inline.go b/inline.go index d45bd941..e13d4257 100644 --- a/inline.go +++ b/inline.go @@ -735,7 +735,9 @@ func linkEndsWithEntity(data []byte, linkEnd int) bool { } // hasPrefixCaseInsensitive is a custom implementation of -// strings.HasPrefix(strings.ToLower(s), prefix) +// +// strings.HasPrefix(strings.ToLower(s), prefix) +// // we rolled our own because ToLower pulls in a huge machinery of lowercasing // anything from Unicode and that's very slow. Since this func will only be // used on ASCII protocol prefixes, we can take shortcuts. diff --git a/markdown.go b/markdown.go index 58d2e453..382db9e9 100644 --- a/markdown.go +++ b/markdown.go @@ -345,8 +345,8 @@ func WithNoExtensions() Option { // In Markdown, the link reference syntax can be made to resolve a link to // a reference instead of an inline URL, in one of the following ways: // -// * [link text][refid] -// * [refid][] +// - [link text][refid] +// - [refid][] // // Usually, the refid is defined at the bottom of the Markdown document. If // this override function is provided, the refid is passed to the override @@ -363,7 +363,9 @@ func WithRefOverride(o ReferenceOverrideFunc) Option { // block of markdown-encoded text. // // The simplest invocation of Run takes one argument, input: -// output := Run(input) +// +// output := Run(input) +// // This will parse the input with CommonExtensions enabled and render it with // the default HTMLRenderer (with CommonHTMLFlags). // @@ -371,13 +373,15 @@ func WithRefOverride(o ReferenceOverrideFunc) Option { // type does not contain exported fields, you can not use it directly. Instead, // use the With* functions. For example, this will call the most basic // functionality, with no extensions: -// output := Run(input, WithNoExtensions()) +// +// output := Run(input, WithNoExtensions()) // // You can use any number of With* arguments, even contradicting ones. They // will be applied in order of appearance and the latter will override the // former: -// output := Run(input, WithNoExtensions(), WithExtensions(exts), -// WithRenderer(yourRenderer)) +// +// output := Run(input, WithNoExtensions(), WithExtensions(exts), +// WithRenderer(yourRenderer)) func Run(input []byte, opts ...Option) []byte { r := NewHTMLRenderer(HTMLRendererParameters{ Flags: CommonHTMLFlags, @@ -491,35 +495,35 @@ func (p *Markdown) parseRefsToAST() { // // Consider this markdown with reference-style links: // -// [link][ref] +// [link][ref] // -// [ref]: /url/ "tooltip title" +// [ref]: /url/ "tooltip title" // // It will be ultimately converted to this HTML: // -//

link

+//

link

// // And a reference structure will be populated as follows: // -// p.refs["ref"] = &reference{ -// link: "/url/", -// title: "tooltip title", -// } +// p.refs["ref"] = &reference{ +// link: "/url/", +// title: "tooltip title", +// } // // Alternatively, reference can contain information about a footnote. Consider // this markdown: // -// Text needing a footnote.[^a] +// Text needing a footnote.[^a] // -// [^a]: This is the note +// [^a]: This is the note // // A reference structure will be populated as follows: // -// p.refs["a"] = &reference{ -// link: "a", -// title: "This is the note", -// noteID: , -// } +// p.refs["a"] = &reference{ +// link: "a", +// title: "This is the note", +// noteID: , +// } // // TODO: As you can see, it begs for splitting into two dedicated structures // for refs and for footnotes.