Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: XML style definitions #693

Merged
merged 1 commit into from Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 12 additions & 7 deletions README.md
Expand Up @@ -230,17 +230,22 @@ formatter outputs raw tokens. The latter is useful for debugging lexers.
<a id="markdown-styles" name="styles"></a>
### Styles

Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
Chroma styles are defined in XML. The style entries use the
[same syntax](http://pygments.org/docs/styles/) as Pygments.

All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
All Pygments styles have been converted to Chroma using the `_tools/style.py`
script.

When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles),
know that the `Background` token type provides the default style for tokens. It does so
by defining a foreground color and background color.

For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
For example, this gives each token name not defined in the style a default color
of `#f8f8f8` and uses `#000000` for the highlighted code block's background:

~~~go
chroma.Background: "#f8f8f2 bg:#000000",
~~~
```xml
<entry type="Background" style="#f8f8f2 bg:#000000"/>
```

Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.

Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions bin/enumer
1 change: 0 additions & 1 deletion bin/stringer

This file was deleted.

2 changes: 1 addition & 1 deletion formatters/html/html.go
Expand Up @@ -520,7 +520,7 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable]
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
classes[chroma.LineLink] = "outline: none; text-decoration:none; color:inherit" + classes[chroma.LineLink]
classes[chroma.LineLink] = "outline: none; text-decoration: none; color: inherit" + classes[chroma.LineLink]
return classes
}

Expand Down
2 changes: 1 addition & 1 deletion formatters/html/html_test.go
Expand Up @@ -222,7 +222,7 @@ func TestTableLinkeableLineNumbers(t *testing.T) {

assert.Contains(t, buf.String(), `id="line1"><a class="lnlinks" href="#line1">1</a>`)
assert.Contains(t, buf.String(), `id="line5"><a class="lnlinks" href="#line5">5</a>`)
assert.Contains(t, buf.String(), `/* LineLinks */ .chroma .lnlinks { outline: none; text-decoration:none; color:inherit }`, buf.String())
assert.Contains(t, buf.String(), `/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }`, buf.String())
}

func TestTableLineNumberSpacing(t *testing.T) {
Expand Down
11 changes: 5 additions & 6 deletions serialise.go
Expand Up @@ -372,13 +372,12 @@ func (t *TokenType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if err := d.DecodeElement(&el, &start); err != nil {
return err
}
for tt, text := range _TokenType_map {
if text == el.Type {
*t = tt
return nil
}
tt, err := TokenTypeString(el.Type)
if err != nil {
return err
}
return fmt.Errorf("unknown TokenType %q", el.Type)
*t = tt
return nil
}

func (t TokenType) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
Expand Down
106 changes: 106 additions & 0 deletions style.go
@@ -1,7 +1,10 @@
package chroma

import (
"encoding/xml"
"fmt"
"io"
"sort"
"strings"
)

Expand Down Expand Up @@ -49,6 +52,10 @@ type StyleEntry struct {
NoInherit bool
}

func (s StyleEntry) MarshalText() ([]byte, error) {
return []byte(s.String()), nil
}

func (s StyleEntry) String() string {
out := []string{}
if s.Bold != Pass {
Expand Down Expand Up @@ -216,6 +223,13 @@ func (s *StyleBuilder) Build() (*Style, error) {
// StyleEntries mapping TokenType to colour definition.
type StyleEntries map[TokenType]string

// NewXMLStyle parses an XML style definition.
func NewXMLStyle(r io.Reader) (*Style, error) {
dec := xml.NewDecoder(r)
style := &Style{}
return style, dec.Decode(style)
}

// NewStyle creates a new style definition.
func NewStyle(name string, entries StyleEntries) (*Style, error) {
return NewStyleBuilder(name).AddAll(entries).Build()
Expand All @@ -239,6 +253,89 @@ type Style struct {
parent *Style
}

func (s *Style) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
if s.parent != nil {
return fmt.Errorf("cannot marshal style with parent")
}
start.Name = xml.Name{Local: "style"}
start.Attr = []xml.Attr{{Name: xml.Name{Local: "name"}, Value: s.Name}}
if err := e.EncodeToken(start); err != nil {
return err
}
sorted := make([]TokenType, 0, len(s.entries))
for ttype := range s.entries {
sorted = append(sorted, ttype)
}
sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
for _, ttype := range sorted {
entry := s.entries[ttype]
el := xml.StartElement{Name: xml.Name{Local: "entry"}}
el.Attr = []xml.Attr{
{Name: xml.Name{Local: "type"}, Value: ttype.String()},
{Name: xml.Name{Local: "style"}, Value: entry.String()},
}
if err := e.EncodeToken(el); err != nil {
return err
}
if err := e.EncodeToken(xml.EndElement{Name: el.Name}); err != nil {
return err
}
}
return e.EncodeToken(xml.EndElement{Name: start.Name})
}

func (s *Style) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
for _, attr := range start.Attr {
if attr.Name.Local == "name" {
s.Name = attr.Value
} else {
return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
}
}
if s.Name == "" {
return fmt.Errorf("missing style name attribute")
}
s.entries = map[TokenType]StyleEntry{}
for {
tok, err := d.Token()
if err != nil {
return err
}
switch el := tok.(type) {
case xml.StartElement:
if el.Name.Local != "entry" {
return fmt.Errorf("unexpected element %s", el.Name.Local)
}
var ttype TokenType
var entry StyleEntry
for _, attr := range el.Attr {
switch attr.Name.Local {
case "type":
ttype, err = TokenTypeString(attr.Value)
if err != nil {
return err
}

case "style":
entry, err = ParseStyleEntry(attr.Value)
if err != nil {
return err
}

default:
return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
}
}
s.entries[ttype] = entry

case xml.EndElement:
if el.Name.Local == start.Name.Local {
return nil
}
}
}
}

// Types that are styled.
func (s *Style) Types() []TokenType {
dedupe := map[TokenType]bool{}
Expand Down Expand Up @@ -319,6 +416,15 @@ func (s *Style) synthesisable(ttype TokenType) bool {
return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
}

// MustParseStyleEntry parses a Pygments style entry or panics.
func MustParseStyleEntry(entry string) StyleEntry {
out, err := ParseStyleEntry(entry)
if err != nil {
panic(err)
}
return out
}

// ParseStyleEntry parses a Pygments style entry.
func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
out := StyleEntry{}
Expand Down
19 changes: 19 additions & 0 deletions style_test.go
@@ -1,6 +1,7 @@
package chroma

import (
"encoding/xml"
"testing"

assert "github.com/alecthomas/assert/v2"
Expand Down Expand Up @@ -101,3 +102,21 @@ func TestStyleBuilderTransform(t *testing.T) {
assert.Equal(t, "#ff0000", orig.Get(NameVariable).Colour.String())
assert.Equal(t, "#ff3300", deriv.Get(NameVariableGlobal).Colour.String())
}

func TestStyleMarshaller(t *testing.T) {
expected, err := NewStyle("test", StyleEntries{
Whitespace: "bg:#ffffff",
Text: "#000000 underline",
})
assert.NoError(t, err)
data, err := xml.MarshalIndent(expected, "", " ")
assert.NoError(t, err)
assert.Equal(t, `<style name="test">
<entry type="Text" style="underline #000000"></entry>
<entry type="TextWhitespace" style="bg:#ffffff"></entry>
</style>`, string(data))
actual := &Style{}
err = xml.Unmarshal(data, actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}
18 changes: 0 additions & 18 deletions styles/abap.go

This file was deleted.

11 changes: 11 additions & 0 deletions styles/abap.xml
@@ -0,0 +1,11 @@
<style name="abap">
<entry type="Error" style="#ff0000"/>
<entry type="Background" style="bg:#ffffff"/>
<entry type="Keyword" style="#0000ff"/>
<entry type="Name" style="#000000"/>
<entry type="LiteralString" style="#55aa22"/>
<entry type="LiteralNumber" style="#33aaff"/>
<entry type="OperatorWord" style="#0000ff"/>
<entry type="Comment" style="italic #888888"/>
<entry type="CommentSpecial" style="#888888"/>
</style>
25 changes: 0 additions & 25 deletions styles/algol.go

This file was deleted.

18 changes: 18 additions & 0 deletions styles/algol.xml
@@ -0,0 +1,18 @@
<style name="algol">
<entry type="Error" style="border:#ff0000"/>
<entry type="Background" style="bg:#ffffff"/>
<entry type="Keyword" style="bold underline"/>
<entry type="KeywordDeclaration" style="italic"/>
<entry type="NameBuiltin" style="bold italic"/>
<entry type="NameBuiltinPseudo" style="bold italic"/>
<entry type="NameClass" style="bold italic #666666"/>
<entry type="NameConstant" style="bold italic #666666"/>
<entry type="NameFunction" style="bold italic #666666"/>
<entry type="NameNamespace" style="bold italic #666666"/>
<entry type="NameVariable" style="bold italic #666666"/>
<entry type="LiteralString" style="italic #666666"/>
<entry type="OperatorWord" style="bold"/>
<entry type="Comment" style="italic #888888"/>
<entry type="CommentSpecial" style="bold noitalic #888888"/>
<entry type="CommentPreproc" style="bold noitalic #888888"/>
</style>
25 changes: 0 additions & 25 deletions styles/algol_nu.go

This file was deleted.

18 changes: 18 additions & 0 deletions styles/algol_nu.xml
@@ -0,0 +1,18 @@
<style name="algol_nu">
<entry type="Error" style="border:#ff0000"/>
<entry type="Background" style="bg:#ffffff"/>
<entry type="Keyword" style="bold"/>
<entry type="KeywordDeclaration" style="italic"/>
<entry type="NameBuiltin" style="bold italic"/>
<entry type="NameBuiltinPseudo" style="bold italic"/>
<entry type="NameClass" style="bold italic #666666"/>
<entry type="NameConstant" style="bold italic #666666"/>
<entry type="NameFunction" style="bold italic #666666"/>
<entry type="NameNamespace" style="bold italic #666666"/>
<entry type="NameVariable" style="bold italic #666666"/>
<entry type="LiteralString" style="italic #666666"/>
<entry type="OperatorWord" style="bold"/>
<entry type="Comment" style="italic #888888"/>
<entry type="CommentSpecial" style="bold noitalic #888888"/>
<entry type="CommentPreproc" style="bold noitalic #888888"/>
</style>