Mercurial > yakumo_izuru > aya
comparison vendor/github.com/alecthomas/chroma/v2/style.go @ 66:787b5ee0289d draft
Use vendored modules
Signed-off-by: Izuru Yakumo <yakumo.izuru@chaotic.ninja>
| author | yakumo.izuru |
|---|---|
| date | Sun, 23 Jul 2023 13:18:53 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 65:6d985efa0f7a | 66:787b5ee0289d |
|---|---|
| 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 } |
