renderer.go 5.0 KB

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