Skip to content

Commit

Permalink
docs: update godocs
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Apr 29, 2024
1 parent bd3952e commit 28bf0c1
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 75 deletions.
71 changes: 49 additions & 22 deletions env.go
Expand Up @@ -8,29 +8,21 @@ import (
"github.com/charmbracelet/x/exp/term"
)

// envNoColor returns true if the environment variables explicitly disable color output
// by setting NO_COLOR (https://no-color.org/)
// or CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
// If CLICOLOR=="0", it will be true only if CLICOLOR_FORCE is also "0" or is unset.
func envNoColor(env map[string]string) bool {
return isTrue(env["NO_COLOR"]) && !isTrue(env["CLICOLOR"]) && !cliColorForced(env)
}

// EnvColorProfile returns the color profile based on environment variables set
// Supports NO_COLOR (https://no-color.org/)
// and CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
// If none of these environment variables are set, this behaves the same as ColorProfile()
// It will return the Ascii color profile if EnvNoColor() returns true
// If the terminal does not support any colors, but CLICOLOR_FORCE is set and not "0"
// then the ANSI color profile will be returned.
func EnvColorProfile(stdout *os.File, environ []string) Profile {
// DetectColorProfile returns the color profile based on the terminal output,
// and environment variables.
//
// If the output is not a terminal, the color profile will be NoTTY unless
// CLICOLOR_FORCE=1 is set. This respects the NO_COLOR and
// CLICOLOR/CLICOLOR_FORCE environment variables.
//
// See https://no-color.org/ and https://bixense.com/clicolors/ for more information.
func DetectColorProfile(stdout *os.File, environ []string) Profile {
if environ == nil {
environ = os.Environ()
}

env := environMap(environ)
p := detectColorProfile(env)
p := envColorProfile(env)
if stdout == nil || !term.IsTerminal(stdout.Fd()) {
p = NoTTY
}
Expand All @@ -41,24 +33,59 @@ func EnvColorProfile(stdout *os.File, environ []string) Profile {

if cliColorForced(env) && p <= Ascii {
p = ANSI
if cp := detectColorProfile(env); cp > p {
if cp := envColorProfile(env); cp > p {
p = cp
}
}

return p
}

// EnvColorProfile returns the color profile based on environment variables.
//
// This respects the NO_COLOR and CLICOLOR/CLICOLOR_FORCE environment
// variables.
//
// See https://no-color.org/ and https://bixense.com/clicolors/ for more information.
func EnvColorProfile(environ []string) Profile {
if environ == nil {
environ = os.Environ()
}

env := environMap(environ)
p := envColorProfile(env)
if envNoColor(env) && p > Ascii {
return Ascii
}

if cliColorForced(env) && p <= Ascii {
p = ANSI
if cp := envColorProfile(env); cp > p {
p = cp
}
}

return p
}

// envNoColor returns true if the environment variables explicitly disable color output
// by setting NO_COLOR (https://no-color.org/)
// or CLICOLOR/CLICOLOR_FORCE (https://bixense.com/clicolors/)
// If NO_COLOR is set, this will return true, ignoring CLICOLOR/CLICOLOR_FORCE
// If CLICOLOR is false, it will be true only if CLICOLOR_FORCE is also false or is unset.
func envNoColor(env map[string]string) bool {
return isTrue(env["NO_COLOR"]) && !isTrue(env["CLICOLOR"]) && !cliColorForced(env)
}

func cliColorForced(env map[string]string) bool {
if forced := env["CLICOLOR_FORCE"]; forced != "" {
return isTrue(forced)
}
return false
}

// detectColorProfile returns the supported color profile:
// Ascii, ANSI, ANSI256, or TrueColor.
func detectColorProfile(env map[string]string) (p Profile) {
// envColorProfile returns infers the color profile from the environment.
func envColorProfile(env map[string]string) (p Profile) {
setProfile := func(profile Profile) {
if profile > p {
p = profile
Expand Down
70 changes: 22 additions & 48 deletions renderer.go
Expand Up @@ -8,14 +8,13 @@ import (
"github.com/lucasb-eyer/go-colorful"
)

// We're manually creating the struct here to avoid initializing the output and
// query the terminal multiple times.
var (
renderer *Renderer
rendererOnce sync.Once
)

// Renderer is a lipgloss terminal renderer.
// Renderer is a terminal style renderer that keep track of the color profile
// and background color detection.
type Renderer struct {
colorProfile Profile
mtx sync.RWMutex
Expand All @@ -41,7 +40,7 @@ func DefaultRenderer() *Renderer {
EnableLegacyWindowsANSI(os.Stdout)
}

cp := EnvColorProfile(os.Stdout, os.Environ())
cp := DetectColorProfile(os.Stdout, os.Environ())
renderer = NewRenderer(cp, hasDarkBackground)
})
return renderer
Expand All @@ -52,46 +51,38 @@ func SetDefaultRenderer(r *Renderer) {
rendererOnce.Do(func() { renderer = r })
}

// NewRenderer creates a new Renderer.
// NewRenderer creates a new Lip Gloss Renderer.
//
// The stdout argument is used to detect if the renderer is writing to a
// terminal. If it is nil, the renderer will assume it's not writing to a
// terminal.
// The environ argument is used to detect the color profile based on the
// environment variables. If it's nil, os.Environ() will be used.
// Set hasDarkBackground to true if the terminal has a dark background.
// It takes a color profile and a boolean indicating whether the terminal has a
// dark background.
// These values are then used to determine how to render colors and styles.
func NewRenderer(cp Profile, hasDarkBackground bool) *Renderer {
return &Renderer{
colorProfile: cp,
hasDarkBackground: hasDarkBackground,
}
}

// ColorProfile returns the detected color profile.
// ColorProfile returns the current color profile.
func (r *Renderer) ColorProfile() Profile {
r.mtx.RLock()
defer r.mtx.RUnlock()

return r.colorProfile
}

// ColorProfile returns the detected color profile.
// ColorProfile returns the ren color profile.
func ColorProfile() Profile {
return DefaultRenderer().ColorProfile()
}

// SetColorProfile sets the color profile on the renderer. This function exists
// mostly for testing purposes so that you can assure you're testing against
// a specific profile.
//
// Outside of testing you likely won't want to use this function as the color
// profile will detect and cache the terminal's color capabilities and choose
// the best available profile.
// SetColorProfile sets the color profile on the renderer.
//
// Available color profiles are:
//
// Ascii // no color, 1-bit
// ANSI //16 colors, 4-bit
// NoTTY // no colors or styles
// Ascii // no colors, other styles are allowed (bold, italic, underline)
// ANSI // 16 colors, 4-bit
// ANSI256 // 256 colors, 8-bit
// TrueColor // 16,777,216 colors, 24-bit
//
Expand All @@ -103,18 +94,13 @@ func (r *Renderer) SetColorProfile(p Profile) {
r.colorProfile = p
}

// SetColorProfile sets the color profile on the default renderer. This
// function exists mostly for testing purposes so that you can assure you're
// testing against a specific profile.
//
// Outside of testing you likely won't want to use this function as the color
// profile will detect and cache the terminal's color capabilities and choose
// the best available profile.
// SetColorProfile sets the color profile on the renderer.
//
// Available color profiles are:
//
// Ascii // no color, 1-bit
// ANSI //16 colors, 4-bit
// NoTTY // no colors or styles
// Ascii // no colors, other styles are allowed (bold, italic, underline)
// ANSI // 16 colors, 4-bit
// ANSI256 // 256 colors, 8-bit
// TrueColor // 16,777,216 colors, 24-bit
//
Expand All @@ -123,14 +109,12 @@ func SetColorProfile(p Profile) {
DefaultRenderer().SetColorProfile(p)
}

// HasDarkBackground returns whether or not the terminal has a dark background.
// HasDarkBackground returns whether or not the renderer has a dark background.
func HasDarkBackground() bool {
return DefaultRenderer().HasDarkBackground()
}

// HasDarkBackground returns whether or not the renderer will render to a dark
// background. A dark background can either be auto-detected, or set explicitly
// on the renderer.
// HasDarkBackground returns whether or not the renderer has a dark background.
func (r *Renderer) HasDarkBackground() bool {
r.mtx.RLock()
defer r.mtx.RUnlock()
Expand All @@ -139,25 +123,15 @@ func (r *Renderer) HasDarkBackground() bool {
}

// SetHasDarkBackground sets the background color detection value for the
// default renderer. This function exists mostly for testing purposes so that
// you can assure you're testing against a specific background color setting.
//
// Outside of testing you likely won't want to use this function as the
// backgrounds value will be automatically detected and cached against the
// terminal's current background color setting.
// default renderer.
//
// This function is thread-safe.
func SetHasDarkBackground(b bool) {
DefaultRenderer().SetHasDarkBackground(b)
}

// SetHasDarkBackground sets the background color detection value on the
// renderer. This function exists mostly for testing purposes so that you can
// assure you're testing against a specific background color setting.
//
// Outside of testing you likely won't want to use this function as the
// backgrounds value will be automatically detected and cached against the
// terminal's current background color setting.
// SetHasDarkBackground sets the background color detection value for the
// default renderer.
//
// This function is thread-safe.
func (r *Renderer) SetHasDarkBackground(b bool) {
Expand Down
8 changes: 4 additions & 4 deletions renderer_test.go
Expand Up @@ -6,12 +6,12 @@ import (
)

func TestRendererHasDarkBackground(t *testing.T) {
r1 := NewRenderer(EnvColorProfile(os.Stdout, nil), true)
r1 := NewRenderer(DetectColorProfile(os.Stdout, nil), true)
r1.SetHasDarkBackground(false)
if r1.HasDarkBackground() {
t.Error("Expected renderer to have light background")
}
r2 := NewRenderer(EnvColorProfile(os.Stdout, nil), false)
r2 := NewRenderer(DetectColorProfile(os.Stdout, nil), false)
r2.SetHasDarkBackground(true)
if !r2.HasDarkBackground() {
t.Error("Expected renderer to have dark background")
Expand All @@ -25,15 +25,15 @@ func TestRendererWithOutput(t *testing.T) {
}
defer f.Close()
defer os.Remove(f.Name())
r := NewRenderer(EnvColorProfile(f, nil), true)
r := NewRenderer(DetectColorProfile(f, nil), true)
r.SetColorProfile(TrueColor)
if r.ColorProfile() != TrueColor {
t.Error("Expected renderer to use true color")
}
}

func TestRace(t *testing.T) {
r := NewRenderer(EnvColorProfile(os.Stdout, nil), true)
r := NewRenderer(DetectColorProfile(os.Stdout, nil), true)

for i := 0; i < 100; i++ {
t.Run("SetColorProfile", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion style_test.go
Expand Up @@ -109,7 +109,7 @@ func TestStyleCustomRender(t *testing.T) {
}

func TestStyleRenderer(t *testing.T) {
r := NewRenderer(EnvColorProfile(os.Stdout, nil), true)
r := NewRenderer(DetectColorProfile(os.Stdout, nil), true)
s1 := NewStyle().Bold(true)
s2 := s1.Renderer(r)
if s1.r == s2.r {
Expand Down

0 comments on commit 28bf0c1

Please sign in to comment.