1
0
mirror of https://github.com/schollz/cowyo.git synced 2023-08-10 21:13:00 +03:00
cowyo/vendor/github.com/sourcegraph/syntaxhighlight/highlight.go
2017-10-03 14:43:55 -04:00

296 lines
6.5 KiB
Go

// Package syntaxhighlight provides syntax highlighting for code. It currently
// uses a language-independent lexer and performs decently on JavaScript, Java,
// Ruby, Python, Go, and C.
package syntaxhighlight
import (
"bytes"
"io"
"strings"
"text/scanner"
"text/template"
"unicode"
"unicode/utf8"
"github.com/sourcegraph/annotate"
)
// Kind represents a syntax highlighting kind (class) which will be assigned to tokens.
// A syntax highlighting scheme (style) maps text style properties to each token kind.
type Kind uint8
// A set of supported highlighting kinds
const (
Whitespace Kind = iota
String
Keyword
Comment
Type
Literal
Punctuation
Plaintext
Tag
HTMLTag
HTMLAttrName
HTMLAttrValue
Decimal
)
//go:generate gostringer -type=Kind
// Printer implements an interface to render highlighted output
// (see HTMLPrinter for the implementation of this interface)
type Printer interface {
Print(w io.Writer, kind Kind, tokText string) error
}
// HTMLConfig holds the HTML class configuration to be used by annotators when
// highlighting code.
type HTMLConfig struct {
String string
Keyword string
Comment string
Type string
Literal string
Punctuation string
Plaintext string
Tag string
HTMLTag string
HTMLAttrName string
HTMLAttrValue string
Decimal string
Whitespace string
AsOrderedList bool
}
// HTMLPrinter implements Printer interface and is used to produce
// HTML-based highligher
type HTMLPrinter HTMLConfig
// Class returns the set class for a given token Kind.
func (c HTMLConfig) Class(kind Kind) string {
switch kind {
case String:
return c.String
case Keyword:
return c.Keyword
case Comment:
return c.Comment
case Type:
return c.Type
case Literal:
return c.Literal
case Punctuation:
return c.Punctuation
case Plaintext:
return c.Plaintext
case Tag:
return c.Tag
case HTMLTag:
return c.HTMLTag
case HTMLAttrName:
return c.HTMLAttrName
case HTMLAttrValue:
return c.HTMLAttrValue
case Decimal:
return c.Decimal
}
return ""
}
// Print is the function that emits highlighted source code using
// <span class="...">...</span> wrapper tags
func (p HTMLPrinter) Print(w io.Writer, kind Kind, tokText string) error {
if p.AsOrderedList {
if i := strings.Index(tokText, "\n"); i > -1 {
if err := p.Print(w, kind, tokText[:i]); err != nil {
return err
}
w.Write([]byte("</li>\n<li>"))
if err := p.Print(w, kind, tokText[i+1:]); err != nil {
return err
}
return nil
}
}
class := ((HTMLConfig)(p)).Class(kind)
if class != "" {
_, err := w.Write([]byte(`<span class="`))
if err != nil {
return err
}
_, err = io.WriteString(w, class)
if err != nil {
return err
}
_, err = w.Write([]byte(`">`))
if err != nil {
return err
}
}
template.HTMLEscape(w, []byte(tokText))
if class != "" {
_, err := w.Write([]byte(`</span>`))
if err != nil {
return err
}
}
return nil
}
type Annotator interface {
Annotate(start int, kind Kind, tokText string) (*annotate.Annotation, error)
}
type HTMLAnnotator HTMLConfig
func (a HTMLAnnotator) Annotate(start int, kind Kind, tokText string) (*annotate.Annotation, error) {
class := ((HTMLConfig)(a)).Class(kind)
if class != "" {
left := []byte(`<span class="`)
left = append(left, []byte(class)...)
left = append(left, []byte(`">`)...)
return &annotate.Annotation{
Start: start, End: start + len(tokText),
Left: left, Right: []byte("</span>"),
}, nil
}
return nil, nil
}
// Option is a type of the function that can modify
// one or more of the options in the HTMLConfig structure.
type Option func(options *HTMLConfig)
// OrderedList allows you to format the output as an ordered list
// to have line numbers in the output.
//
// Example:
// AsHTML(input, OrderedList())
func OrderedList() Option {
return func(o *HTMLConfig) {
o.AsOrderedList = true
}
}
// DefaultHTMLConfig provides class names that match those of google-code-prettify
// (https://code.google.com/p/google-code-prettify/).
var DefaultHTMLConfig = HTMLConfig{
String: "str",
Keyword: "kwd",
Comment: "com",
Type: "typ",
Literal: "lit",
Punctuation: "pun",
Plaintext: "pln",
Tag: "tag",
HTMLTag: "htm",
HTMLAttrName: "atn",
HTMLAttrValue: "atv",
Decimal: "dec",
Whitespace: "",
}
func Print(s *scanner.Scanner, w io.Writer, p Printer) error {
tok := s.Scan()
for tok != scanner.EOF {
tokText := s.TokenText()
err := p.Print(w, tokenKind(tok, tokText), tokText)
if err != nil {
return err
}
tok = s.Scan()
}
return nil
}
func Annotate(src []byte, a Annotator) (annotate.Annotations, error) {
s := NewScanner(src)
var anns annotate.Annotations
read := 0
tok := s.Scan()
for tok != scanner.EOF {
tokText := s.TokenText()
ann, err := a.Annotate(read, tokenKind(tok, tokText), tokText)
if err != nil {
return nil, err
}
read += len(tokText)
if ann != nil {
anns = append(anns, ann)
}
tok = s.Scan()
}
return anns, nil
}
// AsHTML converts source code into an HTML-highlighted version;
// It accepts optional configuration parameters to control rendering
// (see OrderedList as one example)
func AsHTML(src []byte, options ...Option) ([]byte, error) {
opt := DefaultHTMLConfig
for _, f := range options {
f(&opt)
}
var buf bytes.Buffer
if opt.AsOrderedList {
buf.Write([]byte("<ol>\n<li>"))
}
err := Print(NewScanner(src), &buf, HTMLPrinter(opt))
if opt.AsOrderedList {
buf.Write([]byte("</li>\n</ol>"))
}
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// NewScanner is a helper that takes a []byte src, wraps it in a reader and creates a Scanner.
func NewScanner(src []byte) *scanner.Scanner {
return NewScannerReader(bytes.NewReader(src))
}
// NewScannerReader takes a reader src and creates a Scanner.
func NewScannerReader(src io.Reader) *scanner.Scanner {
var s scanner.Scanner
s.Init(src)
s.Error = func(_ *scanner.Scanner, _ string) {}
s.Whitespace = 0
s.Mode = s.Mode ^ scanner.SkipComments
return &s
}
func tokenKind(tok rune, tokText string) Kind {
switch tok {
case scanner.Ident:
if _, isKW := keywords[tokText]; isKW {
return Keyword
}
if r, _ := utf8.DecodeRuneInString(tokText); unicode.IsUpper(r) {
return Type
}
return Plaintext
case scanner.Float, scanner.Int:
return Decimal
case scanner.Char, scanner.String, scanner.RawString:
return String
case scanner.Comment:
return Comment
}
if unicode.IsSpace(tok) {
return Whitespace
}
return Punctuation
}