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. }