codeblock.go

  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}