...

Source file src/github.com/alecthomas/chroma/v2/style.go

Documentation: github.com/alecthomas/chroma/v2

     1  package chroma
     2  
     3  import (
     4  	"encoding/xml"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  )
    10  
    11  // Trilean value for StyleEntry value inheritance.
    12  type Trilean uint8
    13  
    14  // Trilean states.
    15  const (
    16  	Pass Trilean = iota
    17  	Yes
    18  	No
    19  )
    20  
    21  func (t Trilean) String() string {
    22  	switch t {
    23  	case Yes:
    24  		return "Yes"
    25  	case No:
    26  		return "No"
    27  	default:
    28  		return "Pass"
    29  	}
    30  }
    31  
    32  // Prefix returns s with "no" as a prefix if Trilean is no.
    33  func (t Trilean) Prefix(s string) string {
    34  	if t == Yes {
    35  		return s
    36  	} else if t == No {
    37  		return "no" + s
    38  	}
    39  	return ""
    40  }
    41  
    42  // A StyleEntry in the Style map.
    43  type StyleEntry struct {
    44  	// Hex colours.
    45  	Colour     Colour
    46  	Background Colour
    47  	Border     Colour
    48  
    49  	Bold      Trilean
    50  	Italic    Trilean
    51  	Underline Trilean
    52  	NoInherit bool
    53  }
    54  
    55  func (s StyleEntry) MarshalText() ([]byte, error) {
    56  	return []byte(s.String()), nil
    57  }
    58  
    59  func (s StyleEntry) String() string {
    60  	out := []string{}
    61  	if s.Bold != Pass {
    62  		out = append(out, s.Bold.Prefix("bold"))
    63  	}
    64  	if s.Italic != Pass {
    65  		out = append(out, s.Italic.Prefix("italic"))
    66  	}
    67  	if s.Underline != Pass {
    68  		out = append(out, s.Underline.Prefix("underline"))
    69  	}
    70  	if s.NoInherit {
    71  		out = append(out, "noinherit")
    72  	}
    73  	if s.Colour.IsSet() {
    74  		out = append(out, s.Colour.String())
    75  	}
    76  	if s.Background.IsSet() {
    77  		out = append(out, "bg:"+s.Background.String())
    78  	}
    79  	if s.Border.IsSet() {
    80  		out = append(out, "border:"+s.Border.String())
    81  	}
    82  	return strings.Join(out, " ")
    83  }
    84  
    85  // Sub subtracts e from s where elements match.
    86  func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
    87  	out := StyleEntry{}
    88  	if e.Colour != s.Colour {
    89  		out.Colour = s.Colour
    90  	}
    91  	if e.Background != s.Background {
    92  		out.Background = s.Background
    93  	}
    94  	if e.Bold != s.Bold {
    95  		out.Bold = s.Bold
    96  	}
    97  	if e.Italic != s.Italic {
    98  		out.Italic = s.Italic
    99  	}
   100  	if e.Underline != s.Underline {
   101  		out.Underline = s.Underline
   102  	}
   103  	if e.Border != s.Border {
   104  		out.Border = s.Border
   105  	}
   106  	return out
   107  }
   108  
   109  // Inherit styles from ancestors.
   110  //
   111  // Ancestors should be provided from oldest to newest.
   112  func (s StyleEntry) Inherit(ancestors ...StyleEntry) StyleEntry {
   113  	out := s
   114  	for i := len(ancestors) - 1; i >= 0; i-- {
   115  		if out.NoInherit {
   116  			return out
   117  		}
   118  		ancestor := ancestors[i]
   119  		if !out.Colour.IsSet() {
   120  			out.Colour = ancestor.Colour
   121  		}
   122  		if !out.Background.IsSet() {
   123  			out.Background = ancestor.Background
   124  		}
   125  		if !out.Border.IsSet() {
   126  			out.Border = ancestor.Border
   127  		}
   128  		if out.Bold == Pass {
   129  			out.Bold = ancestor.Bold
   130  		}
   131  		if out.Italic == Pass {
   132  			out.Italic = ancestor.Italic
   133  		}
   134  		if out.Underline == Pass {
   135  			out.Underline = ancestor.Underline
   136  		}
   137  	}
   138  	return out
   139  }
   140  
   141  func (s StyleEntry) IsZero() bool {
   142  	return s.Colour == 0 && s.Background == 0 && s.Border == 0 && s.Bold == Pass && s.Italic == Pass &&
   143  		s.Underline == Pass && !s.NoInherit
   144  }
   145  
   146  // A StyleBuilder is a mutable structure for building styles.
   147  //
   148  // Once built, a Style is immutable.
   149  type StyleBuilder struct {
   150  	entries map[TokenType]string
   151  	name    string
   152  	parent  *Style
   153  }
   154  
   155  func NewStyleBuilder(name string) *StyleBuilder {
   156  	return &StyleBuilder{name: name, entries: map[TokenType]string{}}
   157  }
   158  
   159  func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
   160  	for ttype, entry := range entries {
   161  		s.entries[ttype] = entry
   162  	}
   163  	return s
   164  }
   165  
   166  func (s *StyleBuilder) Get(ttype TokenType) StyleEntry {
   167  	// This is less than ideal, but it's the price for not having to check errors on each Add().
   168  	entry, _ := ParseStyleEntry(s.entries[ttype])
   169  	if s.parent != nil {
   170  		entry = entry.Inherit(s.parent.Get(ttype))
   171  	}
   172  	return entry
   173  }
   174  
   175  // Add an entry to the Style map.
   176  //
   177  // See http://pygments.org/docs/styles/#style-rules for details.
   178  func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
   179  	s.entries[ttype] = entry
   180  	return s
   181  }
   182  
   183  func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder {
   184  	s.entries[ttype] = entry.String()
   185  	return s
   186  }
   187  
   188  // Transform passes each style entry currently defined in the builder to the supplied
   189  // function and saves the returned value. This can be used to adjust a style's colours;
   190  // see Colour's ClampBrightness function, for example.
   191  func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder {
   192  	types := make(map[TokenType]struct{})
   193  	for tt := range s.entries {
   194  		types[tt] = struct{}{}
   195  	}
   196  	if s.parent != nil {
   197  		for _, tt := range s.parent.Types() {
   198  			types[tt] = struct{}{}
   199  		}
   200  	}
   201  	for tt := range types {
   202  		s.AddEntry(tt, transform(s.Get(tt)))
   203  	}
   204  	return s
   205  }
   206  
   207  func (s *StyleBuilder) Build() (*Style, error) {
   208  	style := &Style{
   209  		Name:    s.name,
   210  		entries: map[TokenType]StyleEntry{},
   211  		parent:  s.parent,
   212  	}
   213  	for ttype, descriptor := range s.entries {
   214  		entry, err := ParseStyleEntry(descriptor)
   215  		if err != nil {
   216  			return nil, fmt.Errorf("invalid entry for %s: %s", ttype, err)
   217  		}
   218  		style.entries[ttype] = entry
   219  	}
   220  	return style, nil
   221  }
   222  
   223  // StyleEntries mapping TokenType to colour definition.
   224  type StyleEntries map[TokenType]string
   225  
   226  // NewXMLStyle parses an XML style definition.
   227  func NewXMLStyle(r io.Reader) (*Style, error) {
   228  	dec := xml.NewDecoder(r)
   229  	style := &Style{}
   230  	return style, dec.Decode(style)
   231  }
   232  
   233  // MustNewXMLStyle is like NewXMLStyle but panics on error.
   234  func MustNewXMLStyle(r io.Reader) *Style {
   235  	style, err := NewXMLStyle(r)
   236  	if err != nil {
   237  		panic(err)
   238  	}
   239  	return style
   240  }
   241  
   242  // NewStyle creates a new style definition.
   243  func NewStyle(name string, entries StyleEntries) (*Style, error) {
   244  	return NewStyleBuilder(name).AddAll(entries).Build()
   245  }
   246  
   247  // MustNewStyle creates a new style or panics.
   248  func MustNewStyle(name string, entries StyleEntries) *Style {
   249  	style, err := NewStyle(name, entries)
   250  	if err != nil {
   251  		panic(err)
   252  	}
   253  	return style
   254  }
   255  
   256  // A Style definition.
   257  //
   258  // See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
   259  type Style struct {
   260  	Name    string
   261  	entries map[TokenType]StyleEntry
   262  	parent  *Style
   263  }
   264  
   265  func (s *Style) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
   266  	if s.parent != nil {
   267  		return fmt.Errorf("cannot marshal style with parent")
   268  	}
   269  	start.Name = xml.Name{Local: "style"}
   270  	start.Attr = []xml.Attr{{Name: xml.Name{Local: "name"}, Value: s.Name}}
   271  	if err := e.EncodeToken(start); err != nil {
   272  		return err
   273  	}
   274  	sorted := make([]TokenType, 0, len(s.entries))
   275  	for ttype := range s.entries {
   276  		sorted = append(sorted, ttype)
   277  	}
   278  	sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
   279  	for _, ttype := range sorted {
   280  		entry := s.entries[ttype]
   281  		el := xml.StartElement{Name: xml.Name{Local: "entry"}}
   282  		el.Attr = []xml.Attr{
   283  			{Name: xml.Name{Local: "type"}, Value: ttype.String()},
   284  			{Name: xml.Name{Local: "style"}, Value: entry.String()},
   285  		}
   286  		if err := e.EncodeToken(el); err != nil {
   287  			return err
   288  		}
   289  		if err := e.EncodeToken(xml.EndElement{Name: el.Name}); err != nil {
   290  			return err
   291  		}
   292  	}
   293  	return e.EncodeToken(xml.EndElement{Name: start.Name})
   294  }
   295  
   296  func (s *Style) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
   297  	for _, attr := range start.Attr {
   298  		if attr.Name.Local == "name" {
   299  			s.Name = attr.Value
   300  		} else {
   301  			return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
   302  		}
   303  	}
   304  	if s.Name == "" {
   305  		return fmt.Errorf("missing style name attribute")
   306  	}
   307  	s.entries = map[TokenType]StyleEntry{}
   308  	for {
   309  		tok, err := d.Token()
   310  		if err != nil {
   311  			return err
   312  		}
   313  		switch el := tok.(type) {
   314  		case xml.StartElement:
   315  			if el.Name.Local != "entry" {
   316  				return fmt.Errorf("unexpected element %s", el.Name.Local)
   317  			}
   318  			var ttype TokenType
   319  			var entry StyleEntry
   320  			for _, attr := range el.Attr {
   321  				switch attr.Name.Local {
   322  				case "type":
   323  					ttype, err = TokenTypeString(attr.Value)
   324  					if err != nil {
   325  						return err
   326  					}
   327  
   328  				case "style":
   329  					entry, err = ParseStyleEntry(attr.Value)
   330  					if err != nil {
   331  						return err
   332  					}
   333  
   334  				default:
   335  					return fmt.Errorf("unexpected attribute %s", attr.Name.Local)
   336  				}
   337  			}
   338  			s.entries[ttype] = entry
   339  
   340  		case xml.EndElement:
   341  			if el.Name.Local == start.Name.Local {
   342  				return nil
   343  			}
   344  		}
   345  	}
   346  }
   347  
   348  // Types that are styled.
   349  func (s *Style) Types() []TokenType {
   350  	dedupe := map[TokenType]bool{}
   351  	for tt := range s.entries {
   352  		dedupe[tt] = true
   353  	}
   354  	if s.parent != nil {
   355  		for _, tt := range s.parent.Types() {
   356  			dedupe[tt] = true
   357  		}
   358  	}
   359  	out := make([]TokenType, 0, len(dedupe))
   360  	for tt := range dedupe {
   361  		out = append(out, tt)
   362  	}
   363  	return out
   364  }
   365  
   366  // Builder creates a mutable builder from this Style.
   367  //
   368  // The builder can then be safely modified. This is a cheap operation.
   369  func (s *Style) Builder() *StyleBuilder {
   370  	return &StyleBuilder{
   371  		name:    s.Name,
   372  		entries: map[TokenType]string{},
   373  		parent:  s,
   374  	}
   375  }
   376  
   377  // Has checks if an exact style entry match exists for a token type.
   378  //
   379  // This is distinct from Get() which will merge parent tokens.
   380  func (s *Style) Has(ttype TokenType) bool {
   381  	return !s.get(ttype).IsZero() || s.synthesisable(ttype)
   382  }
   383  
   384  // Get a style entry. Will try sub-category or category if an exact match is not found, and
   385  // finally return the Background.
   386  func (s *Style) Get(ttype TokenType) StyleEntry {
   387  	return s.get(ttype).Inherit(
   388  		s.get(Background),
   389  		s.get(Text),
   390  		s.get(ttype.Category()),
   391  		s.get(ttype.SubCategory()))
   392  }
   393  
   394  func (s *Style) get(ttype TokenType) StyleEntry {
   395  	out := s.entries[ttype]
   396  	if out.IsZero() && s.parent != nil {
   397  		return s.parent.get(ttype)
   398  	}
   399  	if out.IsZero() && s.synthesisable(ttype) {
   400  		out = s.synthesise(ttype)
   401  	}
   402  	return out
   403  }
   404  
   405  func (s *Style) synthesise(ttype TokenType) StyleEntry {
   406  	bg := s.get(Background)
   407  	text := StyleEntry{Colour: bg.Colour}
   408  	text.Colour = text.Colour.BrightenOrDarken(0.5)
   409  
   410  	switch ttype {
   411  	// If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
   412  	case LineHighlight:
   413  		return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
   414  
   415  	// If we don't have line numbers, use the text colour but 20% brighter/darker
   416  	case LineNumbers, LineNumbersTable:
   417  		return text
   418  
   419  	default:
   420  		return StyleEntry{}
   421  	}
   422  }
   423  
   424  func (s *Style) synthesisable(ttype TokenType) bool {
   425  	return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
   426  }
   427  
   428  // MustParseStyleEntry parses a Pygments style entry or panics.
   429  func MustParseStyleEntry(entry string) StyleEntry {
   430  	out, err := ParseStyleEntry(entry)
   431  	if err != nil {
   432  		panic(err)
   433  	}
   434  	return out
   435  }
   436  
   437  // ParseStyleEntry parses a Pygments style entry.
   438  func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
   439  	out := StyleEntry{}
   440  	parts := strings.Fields(entry)
   441  	for _, part := range parts {
   442  		switch {
   443  		case part == "italic":
   444  			out.Italic = Yes
   445  		case part == "noitalic":
   446  			out.Italic = No
   447  		case part == "bold":
   448  			out.Bold = Yes
   449  		case part == "nobold":
   450  			out.Bold = No
   451  		case part == "underline":
   452  			out.Underline = Yes
   453  		case part == "nounderline":
   454  			out.Underline = No
   455  		case part == "inherit":
   456  			out.NoInherit = false
   457  		case part == "noinherit":
   458  			out.NoInherit = true
   459  		case part == "bg:":
   460  			out.Background = 0
   461  		case strings.HasPrefix(part, "bg:#"):
   462  			out.Background = ParseColour(part[3:])
   463  			if !out.Background.IsSet() {
   464  				return StyleEntry{}, fmt.Errorf("invalid background colour %q", part)
   465  			}
   466  		case strings.HasPrefix(part, "border:#"):
   467  			out.Border = ParseColour(part[7:])
   468  			if !out.Border.IsSet() {
   469  				return StyleEntry{}, fmt.Errorf("invalid border colour %q", part)
   470  			}
   471  		case strings.HasPrefix(part, "#"):
   472  			out.Colour = ParseColour(part)
   473  			if !out.Colour.IsSet() {
   474  				return StyleEntry{}, fmt.Errorf("invalid colour %q", part)
   475  			}
   476  		default:
   477  			return StyleEntry{}, fmt.Errorf("unknown style element %q", part)
   478  		}
   479  	}
   480  	return out, nil
   481  }
   482  

View as plain text