66
|
1 package chroma
|
|
2
|
|
3 import (
|
|
4 "fmt"
|
|
5 "strings"
|
|
6 )
|
|
7
|
|
8 // Trilean value for StyleEntry value inheritance.
|
|
9 type Trilean uint8
|
|
10
|
|
11 // Trilean states.
|
|
12 const (
|
|
13 Pass Trilean = iota
|
|
14 Yes
|
|
15 No
|
|
16 )
|
|
17
|
|
18 func (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.
|
|
30 func (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.
|
|
40 type 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
|
|
52 func (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.
|
|
79 func (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.
|
|
105 func (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
|
|
134 func (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.
|
|
142 type StyleBuilder struct {
|
|
143 entries map[TokenType]string
|
|
144 name string
|
|
145 parent *Style
|
|
146 }
|
|
147
|
|
148 func NewStyleBuilder(name string) *StyleBuilder {
|
|
149 return &StyleBuilder{name: name, entries: map[TokenType]string{}}
|
|
150 }
|
|
151
|
|
152 func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
|
|
153 for ttype, entry := range entries {
|
|
154 s.entries[ttype] = entry
|
|
155 }
|
|
156 return s
|
|
157 }
|
|
158
|
|
159 func (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.
|
|
171 func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
|
|
172 s.entries[ttype] = entry
|
|
173 return s
|
|
174 }
|
|
175
|
|
176 func (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.
|
|
184 func (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
|
|
200 func (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.
|
|
217 type StyleEntries map[TokenType]string
|
|
218
|
|
219 // NewStyle creates a new style definition.
|
|
220 func NewStyle(name string, entries StyleEntries) (*Style, error) {
|
|
221 return NewStyleBuilder(name).AddAll(entries).Build()
|
|
222 }
|
|
223
|
|
224 // MustNewStyle creates a new style or panics.
|
|
225 func 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.
|
|
236 type Style struct {
|
|
237 Name string
|
|
238 entries map[TokenType]StyleEntry
|
|
239 parent *Style
|
|
240 }
|
|
241
|
|
242 // Types that are styled.
|
|
243 func (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.
|
|
263 func (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.
|
|
274 func (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.
|
|
280 func (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
|
|
288 func (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
|
|
299 func (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
|
|
318 func (s *Style) synthesisable(ttype TokenType) bool {
|
|
319 return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
|
|
320 }
|
|
321
|
|
322 // ParseStyleEntry parses a Pygments style entry.
|
|
323 func 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 }
|