1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-27 21:49:13 +02:00

Move style and formatter API into chroma package.

This commit is contained in:
Alec Thomas 2017-06-06 21:23:50 +10:00
parent ef4a53333b
commit d852022f8d
15 changed files with 261 additions and 253 deletions

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# Chroma - A general purpose syntax highlighter for Go
Chroma is inspired by [Pygments](http://pygments.org/).
## Unsupported Pygments features
- Autodetection from content.

View File

@ -1,4 +1,4 @@
package styles
package chroma
import (
"fmt"

View File

@ -1,4 +1,4 @@
package styles
package chroma
import (
"testing"

18
formatter.go Normal file
View File

@ -0,0 +1,18 @@
package chroma
import (
"io"
)
// A Formatter for Chroma lexers.
type Formatter interface {
// Format returns a formatting function for tokens.
Format(w io.Writer, style *Style) (func(*Token), error)
}
// A FormatterFunc is a Formatter implemented as a function.
type FormatterFunc func(io.Writer, *Style) (func(*Token), error)
func (f FormatterFunc) Format(w io.Writer, s *Style) (func(*Token), error) {
return f(w, s)
}

View File

@ -4,35 +4,23 @@ import (
"io"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/styles"
)
// A Formatter for Chroma lexers.
type Formatter interface {
// Format returns a formatting function for tokens.
Format(w io.Writer, style *styles.Style) (func(*chroma.Token), error)
}
type FormatterFunc func(io.Writer, *styles.Style) (func(*chroma.Token), error)
func (f FormatterFunc) Format(w io.Writer, s *styles.Style) (func(*chroma.Token), error) {
return f(w, s)
}
var noop = Register("noop", FormatterFunc(func(w io.Writer, s *styles.Style) (func(*chroma.Token), error) {
// NoOp formatter.
var NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style) (func(*chroma.Token), error) {
return func(t *chroma.Token) { io.WriteString(w, t.Value) }, nil
}))
// Fallback formatter.
var Fallback = noop
var Fallback = NoOp
// Registry of Formatters.
var Registry = map[string]Formatter{}
var Registry = map[string]chroma.Formatter{}
// Get formatter by name.
//
// If the given formatter is not found, the Fallback formatter will be returned.
func Get(name string) Formatter {
func Get(name string) chroma.Formatter {
if f, ok := Registry[name]; ok {
return f
}
@ -40,7 +28,7 @@ func Get(name string) Formatter {
}
// Register a named formatter.
func Register(name string, formatter Formatter) Formatter {
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
Registry[name] = formatter
return formatter
}

View File

@ -5,11 +5,10 @@ import (
"io"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/styles"
)
// Tokens formatter outputs the raw token structures.
var Tokens = Register("tokens", FormatterFunc(func(w io.Writer, s *styles.Style) (func(*chroma.Token), error) {
var Tokens = Register("tokens", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style) (func(*chroma.Token), error) {
return func(token *chroma.Token) {
fmt.Fprintln(w, token.GoString())
}, nil

View File

@ -5,26 +5,25 @@ import (
"io"
"math"
"github.com/alecthomas/chroma" // nolint
"github.com/alecthomas/chroma/styles"
"github.com/alecthomas/chroma"
)
type ttyTable struct {
foreground map[styles.Colour]string
background map[styles.Colour]string
foreground map[chroma.Colour]string
background map[chroma.Colour]string
}
var c = styles.ParseColour
var c = chroma.ParseColour
var ttyTables = map[int]*ttyTable{
8: &ttyTable{
foreground: map[styles.Colour]string{
foreground: map[chroma.Colour]string{
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
c("#555555"): "\033[90m", c("#ff0000"): "\033[91m", c("#00ff00"): "\033[92m", c("#ffff00"): "\033[93m",
c("#0000ff"): "\033[94m", c("#ff00ff"): "\033[95m", c("#00ffff"): "\033[96m", c("#ffffff"): "\033[97m",
},
background: map[styles.Colour]string{
background: map[chroma.Colour]string{
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
c("#555555"): "\033[100m", c("#ff0000"): "\033[101m", c("#00ff00"): "\033[102m", c("#ffff00"): "\033[103m",
@ -32,7 +31,7 @@ var ttyTables = map[int]*ttyTable{
},
},
256: &ttyTable{
foreground: map[styles.Colour]string{
foreground: map[chroma.Colour]string{
c("#000000"): "\033[38;5;0m", c("#800000"): "\033[38;5;1m", c("#008000"): "\033[38;5;2m", c("#808000"): "\033[38;5;3m",
c("#000080"): "\033[38;5;4m", c("#800080"): "\033[38;5;5m", c("#008080"): "\033[38;5;6m", c("#c0c0c0"): "\033[38;5;7m",
c("#808080"): "\033[38;5;8m", c("#ff0000"): "\033[38;5;9m", c("#00ff00"): "\033[38;5;10m", c("#ffff00"): "\033[38;5;11m",
@ -98,7 +97,7 @@ var ttyTables = map[int]*ttyTable{
c("#a8a8a8"): "\033[38;5;248m", c("#b2b2b2"): "\033[38;5;249m", c("#bcbcbc"): "\033[38;5;250m", c("#c6c6c6"): "\033[38;5;251m",
c("#d0d0d0"): "\033[38;5;252m", c("#dadada"): "\033[38;5;253m", c("#e4e4e4"): "\033[38;5;254m", c("#eeeeee"): "\033[38;5;255m",
},
background: map[styles.Colour]string{
background: map[chroma.Colour]string{
c("#000000"): "\033[48;5;0m", c("#800000"): "\033[48;5;1m", c("#008000"): "\033[48;5;2m", c("#808000"): "\033[48;5;3m",
c("#000080"): "\033[48;5;4m", c("#800080"): "\033[48;5;5m", c("#008080"): "\033[48;5;6m", c("#c0c0c0"): "\033[48;5;7m",
c("#808080"): "\033[48;5;8m", c("#ff0000"): "\033[48;5;9m", c("#00ff00"): "\033[48;5;10m", c("#ffff00"): "\033[48;5;11m",
@ -167,7 +166,7 @@ var ttyTables = map[int]*ttyTable{
},
}
func entryToEscapeSequence(table *ttyTable, entry *styles.Entry) string {
func entryToEscapeSequence(table *ttyTable, entry *chroma.StyleEntry) string {
out := ""
if entry.Bold {
out += "\033[1m"
@ -184,8 +183,8 @@ func entryToEscapeSequence(table *ttyTable, entry *styles.Entry) string {
return out
}
func findClosest(table *ttyTable, colour styles.Colour) styles.Colour {
closestColour := styles.Colour(0)
func findClosest(table *ttyTable, colour chroma.Colour) chroma.Colour {
closestColour := chroma.Colour(0)
closest := math.MaxInt32
for styleColour := range table.foreground {
distance := styleColour.Distance(colour)
@ -197,7 +196,7 @@ func findClosest(table *ttyTable, colour styles.Colour) styles.Colour {
return closestColour
}
func styleToEscapeSequence(table *ttyTable, style *styles.Style) map[chroma.TokenType]string {
func styleToEscapeSequence(table *ttyTable, style *chroma.Style) map[chroma.TokenType]string {
out := map[chroma.TokenType]string{}
for ttype, entry := range style.Entries {
out[ttype] = entryToEscapeSequence(table, entry)
@ -209,7 +208,7 @@ type indexedTTYFormatter struct {
table *ttyTable
}
func (c *indexedTTYFormatter) Format(w io.Writer, style *styles.Style) (func(*chroma.Token), error) {
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style) (func(*chroma.Token), error) {
theme := styleToEscapeSequence(c.table, style)
return func(token *chroma.Token) {
// TODO: Cache token lookups?
@ -219,7 +218,7 @@ func (c *indexedTTYFormatter) Format(w io.Writer, style *styles.Style) (func(*ch
if !ok {
clr, ok = theme[token.Type.Category()]
if !ok {
clr = theme[styles.Inherit]
clr = theme[chroma.InheritStyle]
}
}
}

View File

@ -5,13 +5,12 @@ import (
"io"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/styles"
)
// TTY16m is a true-colour terminal formatter.
var TTY16m = Register("terminal16m", FormatterFunc(trueColourFormatter))
var TTY16m = Register("terminal16m", chroma.FormatterFunc(trueColourFormatter))
func trueColourFormatter(w io.Writer, style *styles.Style) (func(*chroma.Token), error) {
func trueColourFormatter(w io.Writer, style *chroma.Style) (func(*chroma.Token), error) {
return func(token *chroma.Token) {
entry := style.Get(token.Type)
if !entry.IsZero() {

149
style.go Normal file
View File

@ -0,0 +1,149 @@
package chroma
import "strings"
// InheritStyle from entry with this key.
const InheritStyle TokenType = -1
// A StyleEntry in the Style map.
type StyleEntry struct {
// Hex colours.
Colour Colour
Background Colour
Border Colour
Bold bool
Italic bool
Underline bool
}
func (e *StyleEntry) String() string {
out := []string{}
if e.Bold {
out = append(out, "bold")
}
if e.Italic {
out = append(out, "italic")
}
if e.Underline {
out = append(out, "underline")
}
if e.Colour.IsSet() {
out = append(out, e.Colour.String())
}
if e.Background.IsSet() {
out = append(out, "bg:"+e.Background.String())
}
if e.Border.IsSet() {
out = append(out, "border:"+e.Border.String())
}
return strings.Join(out, " ")
}
func (e *StyleEntry) IsZero() bool {
return e.Colour == 0 && e.Background == 0 && e.Border == 0 && !e.Bold && !e.Italic && !e.Underline
}
// StyleEntries mapping TokenType to colour definition.
type StyleEntries map[TokenType]string
// NewStyle creates a new style definition.
func NewStyle(name string, entries StyleEntries) *Style {
s := &Style{
Name: name,
Entries: map[TokenType]*StyleEntry{
InheritStyle: &StyleEntry{},
},
}
for tt, entry := range entries {
s.Add(tt, entry)
}
return s
}
// A Style definition.
//
// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
type Style struct {
Name string
Entries map[TokenType]*StyleEntry
}
// Get a style entry. Will try sub-category or category if an exact match is not found, and
// finally return the entry mapped to `InheritStyle`.
func (s *Style) Get(ttype TokenType) *StyleEntry {
out := s.Entries[ttype]
if out == nil {
out = s.Entries[ttype.SubCategory()]
if out == nil {
out = s.Entries[ttype.Category()]
if out == nil {
out = s.Entries[InheritStyle]
}
}
}
return out
}
// Add an StyleEntry to the Style map.
//
// See http://pygments.org/docs/styles/#style-rules for details.
func (s *Style) Add(ttype TokenType, entry string) *Style { // nolint: gocyclo
out := &StyleEntry{}
dupl := s.Entries[ttype.SubCategory()]
if dupl == nil {
dupl = s.Entries[ttype.Category()]
if dupl == nil {
dupl = s.Entries[InheritStyle]
}
}
parent := &StyleEntry{}
// Duplicate ancestor node.
*parent = *dupl
for _, part := range strings.Fields(entry) {
switch {
case part == "italic":
out.Italic = true
case part == "noitalic":
out.Italic = false
case part == "bold":
out.Bold = true
case part == "nobold":
out.Bold = false
case part == "underline":
out.Underline = true
case part == "nounderline":
out.Underline = false
case part == "noinherit":
parent = &StyleEntry{}
case strings.HasPrefix(part, "bg:#"):
out.Background = ParseColour(part[3:])
case strings.HasPrefix(part, "border:#"):
out.Border = ParseColour(part[7:])
case strings.HasPrefix(part, "#"):
out.Colour = ParseColour(part)
default:
panic("unsupported style entry " + part)
}
}
if parent.Colour != 0 && out.Colour == 0 {
out.Colour = parent.Colour
}
if parent.Background != 0 && out.Background == 0 {
out.Background = parent.Background
}
if parent.Border != 0 && out.Border == 0 {
out.Border = parent.Border
}
if parent.Bold && !out.Bold {
out.Bold = true
}
if parent.Italic && !out.Italic {
out.Italic = true
}
if parent.Underline && !out.Underline {
out.Underline = true
}
s.Entries[ttype] = out
return s
}

30
style_test.go Normal file
View File

@ -0,0 +1,30 @@
package chroma
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestStyleAddNoInherit(t *testing.T) {
s := NewStyle("test", StyleEntries{
Name: "bold #f00",
NameVariable: "noinherit #fff",
})
require.Equal(t, &StyleEntry{Colour: 0x1000000}, s.Get(NameVariable))
}
func TestStyleInherit(t *testing.T) {
s := NewStyle("test", StyleEntries{
Name: "bold #f00",
NameVariable: "#fff",
})
require.Equal(t, &StyleEntry{Colour: 0x1000000, Bold: true}, s.Get(NameVariable))
}
func TestColours(t *testing.T) {
s := NewStyle("test", StyleEntries{
Name: "#f00 bg:#001 border:#ansiblue",
})
require.Equal(t, &StyleEntry{Colour: 0xff0001, Background: 0x000012, Border: 0x000100}, s.Get(Name))
}

View File

@ -1,173 +1,23 @@
package styles
import (
"strings"
"github.com/alecthomas/chroma"
)
import "github.com/alecthomas/chroma"
// Registry of Styles.
var Registry = map[string]*Style{}
var Registry = map[string]*chroma.Style{}
// Fallback style. Reassign to change the default fallback style.
var Fallback = SwapOff
// Register a Style.
func Register(style *Style) *Style {
// Register a chroma.Style.
func Register(style *chroma.Style) *chroma.Style {
Registry[style.Name] = style
return style
}
// Get named style, or Fallback.
func Get(name string) *Style {
func Get(name string) *chroma.Style {
if style, ok := Registry[name]; ok {
return style
}
return Fallback
}
// Inherit from entry with this key.
const Inherit chroma.TokenType = -1
// An Entry in the Style map.
type Entry struct {
// Hex colours.
Colour Colour
Background Colour
Border Colour
Bold bool
Italic bool
Underline bool
}
func (e *Entry) String() string {
out := []string{}
if e.Bold {
out = append(out, "bold")
}
if e.Italic {
out = append(out, "italic")
}
if e.Underline {
out = append(out, "underline")
}
if e.Colour.IsSet() {
out = append(out, e.Colour.String())
}
if e.Background.IsSet() {
out = append(out, "bg:"+e.Background.String())
}
if e.Border.IsSet() {
out = append(out, "border:"+e.Border.String())
}
return strings.Join(out, " ")
}
// Entries mapping TokenType to colour definition.
type Entries map[chroma.TokenType]string
func (e *Entry) IsZero() bool {
return e.Colour == 0 && e.Background == 0 && e.Border == 0 && !e.Bold && !e.Italic && !e.Underline
}
// New creates a new style definition.
func New(name string, entries Entries) *Style {
s := &Style{
Name: name,
Entries: map[chroma.TokenType]*Entry{
Inherit: &Entry{},
},
}
for tt, entry := range entries {
s.Add(tt, entry)
}
return s
}
// A Style definition.
//
// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
type Style struct {
Name string
Entries map[chroma.TokenType]*Entry
}
// Get a style entry. Will try sub-category or category if an exact match is not found, and
// finally return the entry mapped to `Inherit`.
func (s *Style) Get(ttype chroma.TokenType) *Entry {
out := s.Entries[ttype]
if out == nil {
out = s.Entries[ttype.SubCategory()]
if out == nil {
out = s.Entries[ttype.Category()]
if out == nil {
out = s.Entries[Inherit]
}
}
}
return out
}
// Add an Entry to the Style map.
//
// See http://pygments.org/docs/styles/#style-rules for details.
func (s *Style) Add(ttype chroma.TokenType, entry string) *Style { // nolint: gocyclo
out := &Entry{}
dupl := s.Entries[ttype.SubCategory()]
if dupl == nil {
dupl = s.Entries[ttype.Category()]
if dupl == nil {
dupl = s.Entries[Inherit]
}
}
parent := &Entry{}
// Duplicate ancestor node.
*parent = *dupl
for _, part := range strings.Fields(entry) {
switch {
case part == "italic":
out.Italic = true
case part == "noitalic":
out.Italic = false
case part == "bold":
out.Bold = true
case part == "nobold":
out.Bold = false
case part == "underline":
out.Underline = true
case part == "nounderline":
out.Underline = false
case part == "noinherit":
parent = &Entry{}
case strings.HasPrefix(part, "bg:#"):
out.Background = ParseColour(part[3:])
case strings.HasPrefix(part, "border:#"):
out.Border = ParseColour(part[7:])
case strings.HasPrefix(part, "#"):
out.Colour = ParseColour(part)
default:
panic("unsupported style entry " + part)
}
}
if parent.Colour != 0 && out.Colour == 0 {
out.Colour = parent.Colour
}
if parent.Background != 0 && out.Background == 0 {
out.Background = parent.Background
}
if parent.Border != 0 && out.Border == 0 {
out.Border = parent.Border
}
if parent.Bold && !out.Bold {
out.Bold = true
}
if parent.Italic && !out.Italic {
out.Italic = true
}
if parent.Underline && !out.Underline {
out.Underline = true
}
s.Entries[ttype] = out
return s
}

View File

@ -1,32 +0,0 @@
package styles
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/alecthomas/chroma"
)
func TestStyleAddNoInherit(t *testing.T) {
s := New("test", Entries{
chroma.Name: "bold #f00",
chroma.NameVariable: "noinherit #fff",
})
require.Equal(t, &Entry{Colour: 0x1000000}, s.Get(chroma.NameVariable))
}
func TestStyleInherit(t *testing.T) {
s := New("test", Entries{
chroma.Name: "bold #f00",
chroma.NameVariable: "#fff",
})
require.Equal(t, &Entry{Colour: 0x1000000, Bold: true}, s.Get(chroma.NameVariable))
}
func TestColours(t *testing.T) {
s := New("test", Entries{
chroma.Name: "#f00 bg:#001 border:#ansiblue",
})
require.Equal(t, &Entry{Colour: 0xff0001, Background: 0x000012, Border: 0x000100}, s.Get(chroma.Name))
}

View File

@ -1,35 +1,35 @@
package styles
import (
. "github.com/alecthomas/chroma" // nolint: golint
"github.com/alecthomas/chroma"
)
// Borland style.
var Borland = Register(New("borland", Entries{
Whitespace: "#bbbbbb",
var Borland = Register(chroma.NewStyle("borland", chroma.StyleEntries{
chroma.Whitespace: "#bbbbbb",
Comment: "italic #008800",
CommentPreproc: "noitalic #008080",
CommentSpecial: "noitalic bold",
chroma.Comment: "italic #008800",
chroma.CommentPreproc: "noitalic #008080",
chroma.CommentSpecial: "noitalic bold",
String: "#0000FF",
StringChar: "#800080",
Number: "#0000FF",
Keyword: "bold #000080",
OperatorWord: "bold",
NameTag: "bold #000080",
NameAttribute: "#FF0000",
chroma.String: "#0000FF",
chroma.StringChar: "#800080",
chroma.Number: "#0000FF",
chroma.Keyword: "bold #000080",
chroma.OperatorWord: "bold",
chroma.NameTag: "bold #000080",
chroma.NameAttribute: "#FF0000",
GenericHeading: "#999999",
GenericSubheading: "#aaaaaa",
GenericDeleted: "bg:#ffdddd #000000",
GenericInserted: "bg:#ddffdd #000000",
GenericError: "#aa0000",
GenericEmph: "italic",
GenericStrong: "bold",
GenericPrompt: "#555555",
GenericOutput: "#888888",
GenericTraceback: "#aa0000",
chroma.GenericHeading: "#999999",
chroma.GenericSubheading: "#aaaaaa",
chroma.GenericDeleted: "bg:#ffdddd #000000",
chroma.GenericInserted: "bg:#ddffdd #000000",
chroma.GenericError: "#aa0000",
chroma.GenericEmph: "italic",
chroma.GenericStrong: "bold",
chroma.GenericPrompt: "#555555",
chroma.GenericOutput: "#888888",
chroma.GenericTraceback: "#aa0000",
Error: "bg:#e3d2d2 #a61717",
chroma.Error: "bg:#e3d2d2 #a61717",
}))

View File

@ -5,7 +5,7 @@ import (
)
// SwapOff theme.
var SwapOff = Register(New("swapoff", map[chroma.TokenType]string{
var SwapOff = Register(chroma.NewStyle("swapoff", map[chroma.TokenType]string{
chroma.Number: "bold #ansiyellow",
chroma.Comment: "#ansiteal",
chroma.CommentPreproc: "bold #ansigreen",

View File

@ -5,7 +5,7 @@ import (
)
// Trac colour scheme.
var Trac = Register(New("trac", map[chroma.TokenType]string{
var Trac = Register(chroma.NewStyle("trac", map[chroma.TokenType]string{
chroma.Whitespace: "#bbbbbb",
chroma.Comment: "italic #999988",
chroma.CommentPreproc: "bold noitalic #999999",