markdown.go

 1package common
 2
 3import (
 4	"image/color"
 5	"sync"
 6
 7	"charm.land/glamour/v2"
 8	"github.com/alecthomas/chroma/v2/formatters"
 9	"github.com/charmbracelet/crush/internal/ui/styles"
10	"github.com/charmbracelet/crush/internal/ui/xchroma"
11)
12
13const formatterName = "crush"
14
15func init() {
16	// NOTE: Glamour does not offer us an option to pass the formatter
17	// implementation directly. We need to register and use by name.
18	var zero color.Color
19	formatters.Register(formatterName, xchroma.Formatter(zero, nil))
20}
21
22var (
23	mdCacheMu    sync.Mutex
24	mdCache      = map[int]*glamour.TermRenderer{}
25	quietMDCache = map[int]*glamour.TermRenderer{}
26)
27
28// MarkdownRenderer returns a glamour [glamour.TermRenderer] configured with
29// the given styles and width. Renderers are memoized per width and shared
30// across callers; call InvalidateMarkdownRendererCache when the active
31// styles change.
32func MarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer {
33	mdCacheMu.Lock()
34	defer mdCacheMu.Unlock()
35	if r, ok := mdCache[width]; ok {
36		return r
37	}
38	r, _ := glamour.NewTermRenderer(
39		glamour.WithStyles(sty.Markdown),
40		glamour.WithWordWrap(width),
41		glamour.WithChromaFormatter(formatterName),
42	)
43	mdCache[width] = r
44	return r
45}
46
47// QuietMarkdownRenderer returns a glamour [glamour.TermRenderer] with no colors
48// (plain text with structure) and the given width. Renderers are memoized per
49// width and shared across callers.
50func QuietMarkdownRenderer(sty *styles.Styles, width int) *glamour.TermRenderer {
51	mdCacheMu.Lock()
52	defer mdCacheMu.Unlock()
53	if r, ok := quietMDCache[width]; ok {
54		return r
55	}
56	r, _ := glamour.NewTermRenderer(
57		glamour.WithStyles(sty.QuietMarkdown),
58		glamour.WithWordWrap(width),
59		glamour.WithChromaFormatter(formatterName),
60	)
61	quietMDCache[width] = r
62	return r
63}
64
65// InvalidateMarkdownRendererCache drops every cached renderer. Call this
66// whenever the active styles change so subsequent renderers pick up the new
67// ansi.StyleConfig.
68func InvalidateMarkdownRendererCache() {
69	mdCacheMu.Lock()
70	defer mdCacheMu.Unlock()
71	mdCache = map[int]*glamour.TermRenderer{}
72	quietMDCache = map[int]*glamour.TermRenderer{}
73}