style.go

  1package chroma
  2
  3import (
  4	"fmt"
  5	"strings"
  6)
  7
  8// Trilean value for StyleEntry value inheritance.
  9type Trilean uint8
 10
 11// Trilean states.
 12const (
 13	Pass Trilean = iota
 14	Yes
 15	No
 16)
 17
 18func (t Trilean) String() string {
 19	switch t {
 20	case Yes:
 21		return "Yes"
 22	case No:
 23		return "No"
 24	default:
 25		return "Pass"
 26	}
 27}
 28
 29// Prefix returns s with "no" as a prefix if Trilean is no.
 30func (t Trilean) Prefix(s string) string {
 31	if t == Yes {
 32		return s
 33	} else if t == No {
 34		return "no" + s
 35	}
 36	return ""
 37}
 38
 39// A StyleEntry in the Style map.
 40type StyleEntry struct {
 41	// Hex colours.
 42	Colour     Colour
 43	Background Colour
 44	Border     Colour
 45
 46	Bold      Trilean
 47	Italic    Trilean
 48	Underline Trilean
 49	NoInherit bool
 50}
 51
 52func (s StyleEntry) String() string {
 53	out := []string{}
 54	if s.Bold != Pass {
 55		out = append(out, s.Bold.Prefix("bold"))
 56	}
 57	if s.Italic != Pass {
 58		out = append(out, s.Italic.Prefix("italic"))
 59	}
 60	if s.Underline != Pass {
 61		out = append(out, s.Underline.Prefix("underline"))
 62	}
 63	if s.NoInherit {
 64		out = append(out, "noinherit")
 65	}
 66	if s.Colour.IsSet() {
 67		out = append(out, s.Colour.String())
 68	}
 69	if s.Background.IsSet() {
 70		out = append(out, "bg:"+s.Background.String())
 71	}
 72	if s.Border.IsSet() {
 73		out = append(out, "border:"+s.Border.String())
 74	}
 75	return strings.Join(out, " ")
 76}
 77
 78// Sub subtracts e from s where elements match.
 79func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
 80	out := StyleEntry{}
 81	if e.Colour != s.Colour {
 82		out.Colour = s.Colour
 83	}
 84	if e.Background != s.Background {
 85		out.Background = s.Background
 86	}
 87	if e.Bold != s.Bold {
 88		out.Bold = s.Bold
 89	}
 90	if e.Italic != s.Italic {
 91		out.Italic = s.Italic
 92	}
 93	if e.Underline != s.Underline {
 94		out.Underline = s.Underline
 95	}
 96	if e.Border != s.Border {
 97		out.Border = s.Border
 98	}
 99	return out
100}
101
102// Inherit styles from ancestors.
103//
104// Ancestors should be provided from oldest to newest.
105func (s StyleEntry) Inherit(ancestors ...StyleEntry) StyleEntry {
106	out := s
107	for i := len(ancestors) - 1; i >= 0; i-- {
108		if out.NoInherit {
109			return out
110		}
111		ancestor := ancestors[i]
112		if !out.Colour.IsSet() {
113			out.Colour = ancestor.Colour
114		}
115		if !out.Background.IsSet() {
116			out.Background = ancestor.Background
117		}
118		if !out.Border.IsSet() {
119			out.Border = ancestor.Border
120		}
121		if out.Bold == Pass {
122			out.Bold = ancestor.Bold
123		}
124		if out.Italic == Pass {
125			out.Italic = ancestor.Italic
126		}
127		if out.Underline == Pass {
128			out.Underline = ancestor.Underline
129		}
130	}
131	return out
132}
133
134func (s StyleEntry) IsZero() bool {
135	return s.Colour == 0 && s.Background == 0 && s.Border == 0 && s.Bold == Pass && s.Italic == Pass &&
136		s.Underline == Pass && !s.NoInherit
137}
138
139// A StyleBuilder is a mutable structure for building styles.
140//
141// Once built, a Style is immutable.
142type StyleBuilder struct {
143	entries map[TokenType]string
144	name    string
145	parent  *Style
146}
147
148func NewStyleBuilder(name string) *StyleBuilder {
149	return &StyleBuilder{name: name, entries: map[TokenType]string{}}
150}
151
152func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
153	for ttype, entry := range entries {
154		s.entries[ttype] = entry
155	}
156	return s
157}
158
159func (s *StyleBuilder) Get(ttype TokenType) StyleEntry {
160	// This is less than ideal, but it's the price for having to check errors on each Add().
161	entry, _ := ParseStyleEntry(s.entries[ttype])
162	return entry.Inherit(s.parent.Get(ttype))
163}
164
165// Add an entry to the Style map.
166//
167// See http://pygments.org/docs/styles/#style-rules for details.
168func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
169	s.entries[ttype] = entry
170	return s
171}
172
173func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
174	s.entries[ttype] = entry.String()
175	return s
176}
177
178func (s *StyleBuilder) Build() (*Style, error) {
179	style := &Style{
180		Name:    s.name,
181		entries: map[TokenType]StyleEntry{},
182		parent:  s.parent,
183	}
184	for ttype, descriptor := range s.entries {
185		entry, err := ParseStyleEntry(descriptor)
186		if err != nil {
187			return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err)
188		}
189		style.entries[ttype] = entry
190	}
191	return style, nil
192}
193
194// StyleEntries mapping TokenType to colour definition.
195type StyleEntries map[TokenType]string
196
197// NewStyle creates a new style definition.
198func NewStyle(name string, entries StyleEntries) (*Style, error) {
199	return NewStyleBuilder(name).AddAll(entries).Build()
200}
201
202// MustNewStyle creates a new style or panics.
203func MustNewStyle(name string, entries StyleEntries) *Style {
204	style, err := NewStyle(name, entries)
205	if err != nil {
206		panic(err)
207	}
208	return style
209}
210
211// A Style definition.
212//
213// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
214type Style struct {
215	Name    string
216	entries map[TokenType]StyleEntry
217	parent  *Style
218}
219
220// Types that are styled.
221func (s *Style) Types() []TokenType {
222	dedupe := map[TokenType]bool{}
223	for tt := range s.entries {
224		dedupe[tt] = true
225	}
226	if s.parent != nil {
227		for _, tt := range s.parent.Types() {
228			dedupe[tt] = true
229		}
230	}
231	out := make([]TokenType, 0, len(dedupe))
232	for tt := range dedupe {
233		out = append(out, tt)
234	}
235	return out
236}
237
238// Builder creates a mutable builder from this Style.
239//
240// The builder can then be safely modified. This is a cheap operation.
241func (s *Style) Builder() *StyleBuilder {
242	return &StyleBuilder{
243		name:    s.Name,
244		entries: map[TokenType]string{},
245		parent:  s,
246	}
247}
248
249// Has checks if an exact style entry match exists for a token type.
250//
251// This is distinct from Get() which will merge parent tokens.
252func (s *Style) Has(ttype TokenType) bool {
253	return !s.get(ttype).IsZero() || s.synthesisable(ttype)
254}
255
256// Get a style entry. Will try sub-category or category if an exact match is not found, and
257// finally return the Background.
258func (s *Style) Get(ttype TokenType) StyleEntry {
259	return s.get(ttype).Inherit(
260		s.get(Background),
261		s.get(Text),
262		s.get(ttype.Category()),
263		s.get(ttype.SubCategory()))
264}
265
266func (s *Style) get(ttype TokenType) StyleEntry {
267	out := s.entries[ttype]
268	if out.IsZero() && s.synthesisable(ttype) {
269		out = s.synthesise(ttype)
270	}
271	if out.IsZero() && s.parent != nil {
272		return s.parent.get(ttype)
273	}
274	return out
275}
276
277func (s *Style) synthesise(ttype TokenType) StyleEntry {
278	bg := s.get(Background)
279	text := StyleEntry{Colour: bg.Colour}
280	text.Colour = text.Colour.BrightenOrDarken(0.5)
281
282	switch ttype {
283	// If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
284	case LineHighlight:
285		return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
286
287	// If we don't have line numbers, use the text colour but 20% brighter/darker
288	case LineNumbers, LineNumbersTable:
289		return text
290	}
291	return StyleEntry{}
292}
293
294func (s *Style) synthesisable(ttype TokenType) bool {
295	return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
296}
297
298// ParseStyleEntry parses a Pygments style entry.
299func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
300	out := StyleEntry{}
301	parts := strings.Fields(entry)
302	for _, part := range parts {
303		switch {
304		case part == "italic":
305			out.Italic = Yes
306		case part == "noitalic":
307			out.Italic = No
308		case part == "bold":
309			out.Bold = Yes
310		case part == "nobold":
311			out.Bold = No
312		case part == "underline":
313			out.Underline = Yes
314		case part == "nounderline":
315			out.Underline = No
316		case part == "inherit":
317			out.NoInherit = false
318		case part == "noinherit":
319			out.NoInherit = true
320		case part == "bg:":
321			out.Background = 0
322		case strings.HasPrefix(part, "bg:#"):
323			out.Background = ParseColour(part[3:])
324			if !out.Background.IsSet() {
325				return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
326			}
327		case strings.HasPrefix(part, "border:#"):
328			out.Border = ParseColour(part[7:])
329			if !out.Border.IsSet() {
330				return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
331			}
332		case strings.HasPrefix(part, "#"):
333			out.Colour = ParseColour(part)
334			if !out.Colour.IsSet() {
335				return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
336			}
337		default:
338			return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
339		}
340	}
341	return out, nil
342}