forked from NYANDEV/forgejo
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:
parent
ce5f2b9845
commit
af7ffaa279
336 changed files with 37293 additions and 769 deletions
13
vendor/github.com/yuin/goldmark-highlighting/.gitignore
generated
vendored
Normal file
13
vendor/github.com/yuin/goldmark-highlighting/.gitignore
generated
vendored
Normal 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
21
vendor/github.com/yuin/goldmark-highlighting/LICENSE
generated
vendored
Normal 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
66
vendor/github.com/yuin/goldmark-highlighting/README.md
generated
vendored
Normal 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
14
vendor/github.com/yuin/goldmark-highlighting/go.mod
generated
vendored
Normal 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
58
vendor/github.com/yuin/goldmark-highlighting/go.sum
generated
vendored
Normal 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=
|
547
vendor/github.com/yuin/goldmark-highlighting/highlighting.go
generated
vendored
Normal file
547
vendor/github.com/yuin/goldmark-highlighting/highlighting.go
generated
vendored
Normal 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),
|
||||
))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue