source: code/trunk/vendor/github.com/alecthomas/chroma/v2/style.go@ 67

Last change on this file since 67 was 67, checked in by Izuru Yakumo, 23 months ago

Use vendored modules

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 8.6 KB
Line 
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 not having to check errors on each Add().
161 entry, _ := ParseStyleEntry(s.entries[ttype])
162 if s.parent != nil {
163 entry = entry.Inherit(s.parent.Get(ttype))
164 }
165 return entry
166}
167
168// Add an entry to the Style map.
169//
170// See http://pygments.org/docs/styles/#style-rules for details.
171func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
172 s.entries[ttype] = entry
173 return s
174}
175
176func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
177 s.entries[ttype] = entry.String()
178 return s
179}
180
181// Transform passes each style entry currently defined in the builder to the supplied
182// function and saves the returned value. This can be used to adjust a style's colours;
183// see Colour's ClampBrightness function, for example.
184func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder {
185 types := make(map[TokenType]struct{})
186 for tt := range s.entries {
187 types[tt] = struct{}{}
188 }
189 if s.parent != nil {
190 for _, tt := range s.parent.Types() {
191 types[tt] = struct{}{}
192 }
193 }
194 for tt := range types {
195 s.AddEntry(tt, transform(s.Get(tt)))
196 }
197 return s
198}
199
200func (s *StyleBuilder) Build() (*Style, error) {
201 style := &Style{
202 Name: s.name,
203 entries: map[TokenType]StyleEntry{},
204 parent: s.parent,
205 }
206 for ttype, descriptor := range s.entries {
207 entry, err := ParseStyleEntry(descriptor)
208 if err != nil {
209 return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err)
210 }
211 style.entries[ttype] = entry
212 }
213 return style, nil
214}
215
216// StyleEntries mapping TokenType to colour definition.
217type StyleEntries map[TokenType]string
218
219// NewStyle creates a new style definition.
220func NewStyle(name string, entries StyleEntries) (*Style, error) {
221 return NewStyleBuilder(name).AddAll(entries).Build()
222}
223
224// MustNewStyle creates a new style or panics.
225func MustNewStyle(name string, entries StyleEntries) *Style {
226 style, err := NewStyle(name, entries)
227 if err != nil {
228 panic(err)
229 }
230 return style
231}
232
233// A Style definition.
234//
235// See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
236type Style struct {
237 Name string
238 entries map[TokenType]StyleEntry
239 parent *Style
240}
241
242// Types that are styled.
243func (s *Style) Types() []TokenType {
244 dedupe := map[TokenType]bool{}
245 for tt := range s.entries {
246 dedupe[tt] = true
247 }
248 if s.parent != nil {
249 for _, tt := range s.parent.Types() {
250 dedupe[tt] = true
251 }
252 }
253 out := make([]TokenType, 0, len(dedupe))
254 for tt := range dedupe {
255 out = append(out, tt)
256 }
257 return out
258}
259
260// Builder creates a mutable builder from this Style.
261//
262// The builder can then be safely modified. This is a cheap operation.
263func (s *Style) Builder() *StyleBuilder {
264 return &StyleBuilder{
265 name: s.Name,
266 entries: map[TokenType]string{},
267 parent: s,
268 }
269}
270
271// Has checks if an exact style entry match exists for a token type.
272//
273// This is distinct from Get() which will merge parent tokens.
274func (s *Style) Has(ttype TokenType) bool {
275 return !s.get(ttype).IsZero() || s.synthesisable(ttype)
276}
277
278// Get a style entry. Will try sub-category or category if an exact match is not found, and
279// finally return the Background.
280func (s *Style) Get(ttype TokenType) StyleEntry {
281 return s.get(ttype).Inherit(
282 s.get(Background),
283 s.get(Text),
284 s.get(ttype.Category()),
285 s.get(ttype.SubCategory()))
286}
287
288func (s *Style) get(ttype TokenType) StyleEntry {
289 out := s.entries[ttype]
290 if out.IsZero() && s.parent != nil {
291 return s.parent.get(ttype)
292 }
293 if out.IsZero() && s.synthesisable(ttype) {
294 out = s.synthesise(ttype)
295 }
296 return out
297}
298
299func (s *Style) synthesise(ttype TokenType) StyleEntry {
300 bg := s.get(Background)
301 text := StyleEntry{Colour: bg.Colour}
302 text.Colour = text.Colour.BrightenOrDarken(0.5)
303
304 switch ttype {
305 // If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
306 case LineHighlight:
307 return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
308
309 // If we don't have line numbers, use the text colour but 20% brighter/darker
310 case LineNumbers, LineNumbersTable:
311 return text
312
313 default:
314 return StyleEntry{}
315 }
316}
317
318func (s *Style) synthesisable(ttype TokenType) bool {
319 return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
320}
321
322// ParseStyleEntry parses a Pygments style entry.
323func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
324 out := StyleEntry{}
325 parts := strings.Fields(entry)
326 for _, part := range parts {
327 switch {
328 case part == "italic":
329 out.Italic = Yes
330 case part == "noitalic":
331 out.Italic = No
332 case part == "bold":
333 out.Bold = Yes
334 case part == "nobold":
335 out.Bold = No
336 case part == "underline":
337 out.Underline = Yes
338 case part == "nounderline":
339 out.Underline = No
340 case part == "inherit":
341 out.NoInherit = false
342 case part == "noinherit":
343 out.NoInherit = true
344 case part == "bg:":
345 out.Background = 0
346 case strings.HasPrefix(part, "bg:#"):
347 out.Background = ParseColour(part[3:])
348 if !out.Background.IsSet() {
349 return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
350 }
351 case strings.HasPrefix(part, "border:#"):
352 out.Border = ParseColour(part[7:])
353 if !out.Border.IsSet() {
354 return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
355 }
356 case strings.HasPrefix(part, "#"):
357 out.Colour = ParseColour(part)
358 if !out.Colour.IsSet() {
359 return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
360 }
361 default:
362 return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
363 }
364 }
365 return out, nil
366}
Note: See TracBrowser for help on using the repository browser.