1package ansi
2
3import (
4 "fmt"
5 "io"
6 "sync"
7
8 "github.com/alecthomas/chroma/v2"
9 "github.com/alecthomas/chroma/v2/quick"
10 "github.com/alecthomas/chroma/v2/styles"
11)
12
13const (
14 // The chroma style theme name used for rendering.
15 chromaStyleTheme = "charm"
16
17 // The chroma formatter name used for rendering.
18 chromaFormatter = "terminal256"
19)
20
21// mutex for synchronizing access to the chroma style registry.
22// Related https://github.com/alecthomas/chroma/pull/650
23var mutex = sync.Mutex{}
24
25// A CodeBlockElement is used to render code blocks.
26type CodeBlockElement struct {
27 Code string
28 Language string
29}
30
31func chromaStyle(style StylePrimitive) string {
32 var s string
33
34 if style.Color != nil {
35 s = *style.Color
36 }
37 if style.BackgroundColor != nil {
38 if s != "" {
39 s += " "
40 }
41 s += "bg:" + *style.BackgroundColor
42 }
43 if style.Italic != nil && *style.Italic {
44 if s != "" {
45 s += " "
46 }
47 s += "italic"
48 }
49 if style.Bold != nil && *style.Bold {
50 if s != "" {
51 s += " "
52 }
53 s += "bold"
54 }
55 if style.Underline != nil && *style.Underline {
56 if s != "" {
57 s += " "
58 }
59 s += "underline"
60 }
61
62 return s
63}
64
65// Render renders a CodeBlockElement.
66func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
67 bs := ctx.blockStack
68
69 var indentation uint
70 var margin uint
71 formatter := chromaFormatter
72 rules := ctx.options.Styles.CodeBlock
73 if rules.Indent != nil {
74 indentation = *rules.Indent
75 }
76 if rules.Margin != nil {
77 margin = *rules.Margin
78 }
79 if len(ctx.options.ChromaFormatter) > 0 {
80 formatter = ctx.options.ChromaFormatter
81 }
82 theme := rules.Theme
83
84 if rules.Chroma != nil {
85 theme = chromaStyleTheme
86 mutex.Lock()
87 // Don't register the style if it's already registered.
88 _, ok := styles.Registry[theme]
89 if !ok {
90 styles.Register(chroma.MustNewStyle(theme,
91 chroma.StyleEntries{
92 chroma.Text: chromaStyle(rules.Chroma.Text),
93 chroma.Error: chromaStyle(rules.Chroma.Error),
94 chroma.Comment: chromaStyle(rules.Chroma.Comment),
95 chroma.CommentPreproc: chromaStyle(rules.Chroma.CommentPreproc),
96 chroma.Keyword: chromaStyle(rules.Chroma.Keyword),
97 chroma.KeywordReserved: chromaStyle(rules.Chroma.KeywordReserved),
98 chroma.KeywordNamespace: chromaStyle(rules.Chroma.KeywordNamespace),
99 chroma.KeywordType: chromaStyle(rules.Chroma.KeywordType),
100 chroma.Operator: chromaStyle(rules.Chroma.Operator),
101 chroma.Punctuation: chromaStyle(rules.Chroma.Punctuation),
102 chroma.Name: chromaStyle(rules.Chroma.Name),
103 chroma.NameBuiltin: chromaStyle(rules.Chroma.NameBuiltin),
104 chroma.NameTag: chromaStyle(rules.Chroma.NameTag),
105 chroma.NameAttribute: chromaStyle(rules.Chroma.NameAttribute),
106 chroma.NameClass: chromaStyle(rules.Chroma.NameClass),
107 chroma.NameConstant: chromaStyle(rules.Chroma.NameConstant),
108 chroma.NameDecorator: chromaStyle(rules.Chroma.NameDecorator),
109 chroma.NameException: chromaStyle(rules.Chroma.NameException),
110 chroma.NameFunction: chromaStyle(rules.Chroma.NameFunction),
111 chroma.NameOther: chromaStyle(rules.Chroma.NameOther),
112 chroma.Literal: chromaStyle(rules.Chroma.Literal),
113 chroma.LiteralNumber: chromaStyle(rules.Chroma.LiteralNumber),
114 chroma.LiteralDate: chromaStyle(rules.Chroma.LiteralDate),
115 chroma.LiteralString: chromaStyle(rules.Chroma.LiteralString),
116 chroma.LiteralStringEscape: chromaStyle(rules.Chroma.LiteralStringEscape),
117 chroma.GenericDeleted: chromaStyle(rules.Chroma.GenericDeleted),
118 chroma.GenericEmph: chromaStyle(rules.Chroma.GenericEmph),
119 chroma.GenericInserted: chromaStyle(rules.Chroma.GenericInserted),
120 chroma.GenericStrong: chromaStyle(rules.Chroma.GenericStrong),
121 chroma.GenericSubheading: chromaStyle(rules.Chroma.GenericSubheading),
122 chroma.Background: chromaStyle(rules.Chroma.Background),
123 }))
124 }
125 mutex.Unlock()
126 }
127
128 iw := NewIndentWriter(w, int(indentation+margin), func(_ io.Writer) { //nolint:gosec
129 _, _ = renderText(w, bs.Current().Style.StylePrimitive, " ")
130 })
131
132 if len(theme) > 0 {
133 _, _ = renderText(iw, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
134
135 err := quick.Highlight(iw, e.Code, e.Language, formatter, theme)
136 if err != nil {
137 return fmt.Errorf("glamour: error highlighting code: %w", err)
138 }
139 _, _ = renderText(iw, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
140 return nil
141 }
142
143 // fallback rendering
144 el := &BaseElement{
145 Token: e.Code,
146 Style: rules.StylePrimitive,
147 }
148
149 return el.Render(iw, ctx)
150}