main
1package lipgloss
2
3import (
4 "io"
5 "sync"
6
7 "github.com/muesli/termenv"
8)
9
10// We're manually creating the struct here to avoid initializing the output and
11// query the terminal multiple times.
12var renderer = &Renderer{
13 output: termenv.DefaultOutput(),
14}
15
16// Renderer is a lipgloss terminal renderer.
17type Renderer struct {
18 output *termenv.Output
19 colorProfile termenv.Profile
20 hasDarkBackground bool
21
22 getColorProfile sync.Once
23 explicitColorProfile bool
24
25 getBackgroundColor sync.Once
26 explicitBackgroundColor bool
27
28 mtx sync.RWMutex
29}
30
31// DefaultRenderer returns the default renderer.
32func DefaultRenderer() *Renderer {
33 return renderer
34}
35
36// SetDefaultRenderer sets the default global renderer.
37func SetDefaultRenderer(r *Renderer) {
38 renderer = r
39}
40
41// NewRenderer creates a new Renderer.
42//
43// w will be used to determine the terminal's color capabilities.
44func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer {
45 r := &Renderer{
46 output: termenv.NewOutput(w, opts...),
47 }
48 return r
49}
50
51// Output returns the termenv output.
52func (r *Renderer) Output() *termenv.Output {
53 r.mtx.RLock()
54 defer r.mtx.RUnlock()
55 return r.output
56}
57
58// SetOutput sets the termenv output.
59func (r *Renderer) SetOutput(o *termenv.Output) {
60 r.mtx.Lock()
61 defer r.mtx.Unlock()
62 r.output = o
63}
64
65// ColorProfile returns the detected termenv color profile.
66func (r *Renderer) ColorProfile() termenv.Profile {
67 r.mtx.RLock()
68 defer r.mtx.RUnlock()
69
70 if !r.explicitColorProfile {
71 r.getColorProfile.Do(func() {
72 // NOTE: we don't need to lock here because sync.Once provides its
73 // own locking mechanism.
74 r.colorProfile = r.output.EnvColorProfile()
75 })
76 }
77
78 return r.colorProfile
79}
80
81// ColorProfile returns the detected termenv color profile.
82func ColorProfile() termenv.Profile {
83 return renderer.ColorProfile()
84}
85
86// SetColorProfile sets the color profile on the renderer. This function exists
87// mostly for testing purposes so that you can assure you're testing against
88// a specific profile.
89//
90// Outside of testing you likely won't want to use this function as the color
91// profile will detect and cache the terminal's color capabilities and choose
92// the best available profile.
93//
94// Available color profiles are:
95//
96// termenv.Ascii // no color, 1-bit
97// termenv.ANSI //16 colors, 4-bit
98// termenv.ANSI256 // 256 colors, 8-bit
99// termenv.TrueColor // 16,777,216 colors, 24-bit
100//
101// This function is thread-safe.
102func (r *Renderer) SetColorProfile(p termenv.Profile) {
103 r.mtx.Lock()
104 defer r.mtx.Unlock()
105
106 r.colorProfile = p
107 r.explicitColorProfile = true
108}
109
110// SetColorProfile sets the color profile on the default renderer. This
111// function exists mostly for testing purposes so that you can assure you're
112// testing against a specific profile.
113//
114// Outside of testing you likely won't want to use this function as the color
115// profile will detect and cache the terminal's color capabilities and choose
116// the best available profile.
117//
118// Available color profiles are:
119//
120// termenv.Ascii // no color, 1-bit
121// termenv.ANSI //16 colors, 4-bit
122// termenv.ANSI256 // 256 colors, 8-bit
123// termenv.TrueColor // 16,777,216 colors, 24-bit
124//
125// This function is thread-safe.
126func SetColorProfile(p termenv.Profile) {
127 renderer.SetColorProfile(p)
128}
129
130// HasDarkBackground returns whether or not the terminal has a dark background.
131func HasDarkBackground() bool {
132 return renderer.HasDarkBackground()
133}
134
135// HasDarkBackground returns whether or not the renderer will render to a dark
136// background. A dark background can either be auto-detected, or set explicitly
137// on the renderer.
138func (r *Renderer) HasDarkBackground() bool {
139 r.mtx.RLock()
140 defer r.mtx.RUnlock()
141
142 if !r.explicitBackgroundColor {
143 r.getBackgroundColor.Do(func() {
144 // NOTE: we don't need to lock here because sync.Once provides its
145 // own locking mechanism.
146 r.hasDarkBackground = r.output.HasDarkBackground()
147 })
148 }
149
150 return r.hasDarkBackground
151}
152
153// SetHasDarkBackground sets the background color detection value for the
154// default renderer. This function exists mostly for testing purposes so that
155// you can assure you're testing against a specific background color setting.
156//
157// Outside of testing you likely won't want to use this function as the
158// backgrounds value will be automatically detected and cached against the
159// terminal's current background color setting.
160//
161// This function is thread-safe.
162func SetHasDarkBackground(b bool) {
163 renderer.SetHasDarkBackground(b)
164}
165
166// SetHasDarkBackground sets the background color detection value on the
167// renderer. This function exists mostly for testing purposes so that you can
168// assure you're testing against a specific background color setting.
169//
170// Outside of testing you likely won't want to use this function as the
171// backgrounds value will be automatically detected and cached against the
172// terminal's current background color setting.
173//
174// This function is thread-safe.
175func (r *Renderer) SetHasDarkBackground(b bool) {
176 r.mtx.Lock()
177 defer r.mtx.Unlock()
178
179 r.hasDarkBackground = b
180 r.explicitBackgroundColor = true
181}