Server-side syntax highlighting for all code (#12047)

* Server-side syntax hilighting for all code

This PR does a few things:

* Remove all traces of highlight.js
* Use chroma library to provide fast syntax hilighting directly on the server
* Provide syntax hilighting for diffs
* Re-style both unified and split diffs views
* Add custom syntax hilighting styling for both regular and arc-green

Fixes #7729
Fixes #10157
Fixes #11825
Fixes #7728
Fixes #3872
Fixes #3682

And perhaps gets closer to #9553

* fix line marker

* fix repo search

* Fix single line select

* properly load settings

* npm uninstall highlight.js

* review suggestion

* code review

* forgot to call function

* fix test

* Apply suggestions from code review

suggestions from @silverwind thanks

Co-authored-by: silverwind <me@silverwind.io>

* code review

* copy/paste error

* Use const for highlight size limit

* Update web_src/less/_repository.less

Co-authored-by: Lauris BH <lauris@nix.lv>

* update size limit to 1MB and other styling tweaks

* fix highlighting for certain diff sections

* fix test

* add worker back as suggested

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
mrsdizzie 2020-06-30 17:34:03 -04:00 committed by GitHub
parent ce5f2b9845
commit af7ffaa279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
336 changed files with 37293 additions and 769 deletions

View file

@ -0,0 +1,13 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
*.pprof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

21
vendor/github.com/yuin/goldmark-highlighting/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Yusuke Inuzuka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

66
vendor/github.com/yuin/goldmark-highlighting/README.md generated vendored Normal file
View file

@ -0,0 +1,66 @@
goldmark-highlighting
=========================
goldmark-highlighting is an extension for the [goldmark](http://github.com/yuin/goldmark)
that adds syntax-highlighting to the fenced code blocks.
goldmark-highlighting uses [chroma](https://github.com/alecthomas/chroma) as a
syntax highlighter.
Installation
--------------------
```
go get github.com/yuin/goldmark-highlighting
```
Usage
--------------------
```go
import (
"bytes"
"fmt"
"github.com/alecthomas/chroma/formatters/html"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark-highlighting"
)
func main() {
markdown := goldmark.New(
goldmark.WithExtensions(
highlighting.Highlighting,
),
)
var buf bytes.Buffer
if err := markdown.Convert([]byte(source), &buf); err != nil {
panic(err)
}
fmt.Print(title)
}
```
```go
markdown := goldmark.New(
goldmark.WithExtensions(
highlighting.NewHighlighting(
highlighting.WithStyle("monokai"),
highlighting.WithFormatOptions(
html.WithLineNumbers(),
),
),
),
)
```
License
--------------------
MIT
Author
--------------------
Yusuke Inuzuka

14
vendor/github.com/yuin/goldmark-highlighting/go.mod generated vendored Normal file
View file

@ -0,0 +1,14 @@
module github.com/yuin/goldmark-highlighting
go 1.13
require (
github.com/GeertJohan/go.rice v1.0.0 // indirect
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8 // indirect
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/gorilla/csrf v1.6.0 // indirect
github.com/gorilla/handlers v1.4.1 // indirect
github.com/gorilla/mux v1.7.3 // indirect
github.com/yuin/goldmark v1.1.22
)

58
vendor/github.com/yuin/goldmark-highlighting/go.sum generated vendored Normal file
View file

@ -0,0 +1,58 @@
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw=
github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY=
github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ=
github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc=
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a h1:3v1NrYWWqp2S72e4HLgxKt83B3l0lnORDholH/ihoMM=
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
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.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/yuin/goldmark v1.1.7 h1:XiwWADvxJeIM1JbXqthrEhDc19hTMui+o+QaY1hGXlk=
github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.22 h1:0e0f6Zee9SAQ5yOZGNMWaOxqVvcc/9/kUWu/Kl91Jk8=
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -0,0 +1,547 @@
// 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).
package highlighting
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
// 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 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"
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 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[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 || 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("<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),
))
}