...

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

Documentation: github.com/alecthomas/chroma

     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 having to check errors on each Add().
   161  	entry, _ := ParseStyleEntry(s.entries[ttype])
   162  	return entry.Inherit(s.parent.Get(ttype))
   163  }
   164  
   165  // Add an entry to the Style map.
   166  //
   167  // See http://pygments.org/docs/styles/#style-rules for details.
   168  func (s *StyleBuilder) Add(ttype TokenType, entry string) *StyleBuilder { // nolint: gocyclo
   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  // StyleEntries mapping TokenType to colour definition.
   195  type StyleEntries map[TokenType]string
   196  
   197  // NewStyle creates a new style definition.
   198  func NewStyle(name string, entries StyleEntries) (*Style, error) {
   199  	return NewStyleBuilder(name).AddAll(entries).Build()
   200  }
   201  
   202  // MustNewStyle creates a new style or panics.
   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  // A Style definition.
   212  //
   213  // See http://pygments.org/docs/styles/ for details. Semantics are intended to be identical.
   214  type Style struct {
   215  	Name    string
   216  	entries map[TokenType]StyleEntry
   217  	parent  *Style
   218  }
   219  
   220  // Types that are styled.
   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  // Builder creates a mutable builder from this Style.
   239  //
   240  // The builder can then be safely modified. This is a cheap operation.
   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  // Has checks if an exact style entry match exists for a token type.
   250  //
   251  // This is distinct from Get() which will merge parent tokens.
   252  func (s *Style) Has(ttype TokenType) bool {
   253  	return !s.get(ttype).IsZero() || s.synthesisable(ttype)
   254  }
   255  
   256  // Get a style entry. Will try sub-category or category if an exact match is not found, and
   257  // finally return the Background.
   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  	// If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
   284  	case LineHighlight:
   285  		return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
   286  
   287  	// If we don't have line numbers, use the text colour but 20% brighter/darker
   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  // ParseStyleEntry parses a Pygments style entry.
   301  func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
   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