1 package chroma
2
3 import (
4 "fmt"
5 "strings"
6 )
7
8
9 type Trilean uint8
10
11
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
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
40 type StyleEntry struct {
41
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
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
103
104
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
140
141
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
161 entry, _ := ParseStyleEntry(s.entries[ttype])
162 return entry.Inherit(s.parent.Get(ttype))
163 }
164
165
166
167
168 func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder {
169 s.entries[ttype] = entry
170 return s
171 }
172
173 func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
174 s.entries[ttype] = entry.String()
175 return s
176 }
177
178 func (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
195 type StyleEntries map[TokenType]string
196
197
198 func NewStyle(name string, entries StyleEntries) (*Style, error) {
199 return NewStyleBuilder(name).AddAll(entries).Build()
200 }
201
202
203 func 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
212
213
214 type Style struct {
215 Name string
216 entries map[TokenType]StyleEntry
217 parent *Style
218 }
219
220
221 func (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
239
240
241 func (s *Style) Builder() *StyleBuilder {
242 return &StyleBuilder{
243 name: s.Name,
244 entries: map[TokenType]string{},
245 parent: s,
246 }
247 }
248
249
250
251
252 func (s *Style) Has(ttype TokenType) bool {
253 return !s.get(ttype).IsZero() || s.synthesisable(ttype)
254 }
255
256
257
258 func (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
266 func (s *Style) get(ttype TokenType) StyleEntry {
267 out := s.entries[ttype]
268 if out.IsZero() && s.parent != nil {
269 return s.parent.get(ttype)
270 }
271 if out.IsZero() && s.synthesisable(ttype) {
272 out = s.synthesise(ttype)
273 }
274 return out
275 }
276
277 func (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
284 case LineHighlight:
285 return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
286
287
288 case LineNumbers, LineNumbersTable:
289 return text
290
291 default:
292 return StyleEntry{}
293 }
294 }
295
296 func (s *Style) synthesisable(ttype TokenType) bool {
297 return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
298 }
299
300
301 func ParseStyleEntry(entry string) (StyleEntry, error) {
302 out := StyleEntry{}
303 parts := strings.Fields(entry)
304 for _, part := range parts {
305 switch {
306 case part == "italic":
307 out.Italic = Yes
308 case part == "noitalic":
309 out.Italic = No
310 case part == "bold":
311 out.Bold = Yes
312 case part == "nobold":
313 out.Bold = No
314 case part == "underline":
315 out.Underline = Yes
316 case part == "nounderline":
317 out.Underline = No
318 case part == "inherit":
319 out.NoInherit = false
320 case part == "noinherit":
321 out.NoInherit = true
322 case part == "bg:":
323 out.Background = 0
324 case strings.HasPrefix(part, "bg:#"):
325 out.Background = ParseColour(part[3:])
326 if !out.Background.IsSet() {
327 return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
328 }
329 case strings.HasPrefix(part, "border:#"):
330 out.Border = ParseColour(part[7:])
331 if !out.Border.IsSet() {
332 return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
333 }
334 case strings.HasPrefix(part, "#"):
335 out.Colour = ParseColour(part)
336 if !out.Colour.IsSet() {
337 return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
338 }
339 default:
340 return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
341 }
342 }
343 return out, nil
344 }
345
View as plain text