diff --git a/color_test.go b/color_test.go index 2825a29..7cec7af 100644 --- a/color_test.go +++ b/color_test.go @@ -5,52 +5,6 @@ import ( "testing" ) -func TestSetColorProfile(t *testing.T) { - r := DefaultRenderer() - input := "hello" - - tt := []struct { - name string - profile Profile - expected string - }{ - { - "ascii", - Ascii, - "hello", - }, - { - "ansi", - ANSI, - "\x1b[94mhello\x1b[m", - }, - { - "ansi256", - ANSI256, - "\x1b[38;5;62mhello\x1b[m", - }, - { - "truecolor", - TrueColor, - "\x1b[38;2;89;86;224mhello\x1b[m", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - r.SetColorProfile(tc.profile) - style := NewStyle().Foreground(Color("#5A56E0")) - res := style.Render(input) - - if res != tc.expected { - t.Errorf("Expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", - tc.expected, formatEscapes(tc.expected), - res, formatEscapes(res)) - } - }) - } -} - func TestHexToColor(t *testing.T) { t.Parallel() diff --git a/renderer.go b/renderer.go deleted file mode 100644 index 233aa7c..0000000 --- a/renderer.go +++ /dev/null @@ -1,181 +0,0 @@ -package lipgloss - -import ( - "io" - "sync" - - "github.com/muesli/termenv" -) - -// We're manually creating the struct here to avoid initializing the output and -// query the terminal multiple times. -var renderer = &Renderer{ - output: termenv.DefaultOutput(), -} - -// Renderer is a lipgloss terminal renderer. -type Renderer struct { - output *termenv.Output - colorProfile termenv.Profile - hasDarkBackground bool - - getColorProfile sync.Once - explicitColorProfile bool - - getBackgroundColor sync.Once - explicitBackgroundColor bool - - mtx sync.RWMutex -} - -// DefaultRenderer returns the default renderer. -func DefaultRenderer() *Renderer { - return renderer -} - -// SetDefaultRenderer sets the default global renderer. -func SetDefaultRenderer(r *Renderer) { - renderer = r -} - -// NewRenderer creates a new Renderer. -// -// w will be used to determine the terminal's color capabilities. -func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer { - r := &Renderer{ - output: termenv.NewOutput(w, opts...), - } - return r -} - -// Output returns the termenv output. -func (r *Renderer) Output() *termenv.Output { - r.mtx.RLock() - defer r.mtx.RUnlock() - return r.output -} - -// SetOutput sets the termenv output. -func (r *Renderer) SetOutput(o *termenv.Output) { - r.mtx.Lock() - defer r.mtx.Unlock() - r.output = o -} - -// ColorProfile returns the detected termenv color profile. -func (r *Renderer) ColorProfile() termenv.Profile { - r.mtx.RLock() - defer r.mtx.RUnlock() - - if !r.explicitColorProfile { - r.getColorProfile.Do(func() { - // NOTE: we don't need to lock here because sync.Once provides its - // own locking mechanism. - r.colorProfile = r.output.EnvColorProfile() - }) - } - - return r.colorProfile -} - -// ColorProfile returns the detected termenv color profile. -func ColorProfile() termenv.Profile { - return renderer.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. -// -// Available color profiles are: -// -// termenv.Ascii // no color, 1-bit -// termenv.ANSI //16 colors, 4-bit -// termenv.ANSI256 // 256 colors, 8-bit -// termenv.TrueColor // 16,777,216 colors, 24-bit -// -// This function is thread-safe. -func (r *Renderer) SetColorProfile(p termenv.Profile) { - r.mtx.Lock() - defer r.mtx.Unlock() - - r.colorProfile = p - r.explicitColorProfile = true -} - -// 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. -// -// Available color profiles are: -// -// termenv.Ascii // no color, 1-bit -// termenv.ANSI //16 colors, 4-bit -// termenv.ANSI256 // 256 colors, 8-bit -// termenv.TrueColor // 16,777,216 colors, 24-bit -// -// This function is thread-safe. -func SetColorProfile(p termenv.Profile) { - renderer.SetColorProfile(p) -} - -// HasDarkBackground returns whether or not the terminal has a dark background. -func HasDarkBackground() bool { - return renderer.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. -func (r *Renderer) HasDarkBackground() bool { - r.mtx.RLock() - defer r.mtx.RUnlock() - - if !r.explicitBackgroundColor { - r.getBackgroundColor.Do(func() { - // NOTE: we don't need to lock here because sync.Once provides its - // own locking mechanism. - r.hasDarkBackground = r.output.HasDarkBackground() - }) - } - - return r.hasDarkBackground -} - -// 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. -// -// This function is thread-safe. -func SetHasDarkBackground(b bool) { - renderer.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. -// -// This function is thread-safe. -func (r *Renderer) SetHasDarkBackground(b bool) { - r.mtx.Lock() - defer r.mtx.Unlock() - - r.hasDarkBackground = b - r.explicitBackgroundColor = true -} diff --git a/renderer_test.go b/renderer_test.go deleted file mode 100644 index 6c0b145..0000000 --- a/renderer_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package lipgloss - -import ( - "io" - "os" - "testing" - - "github.com/muesli/termenv" -) - -func TestRendererHasDarkBackground(t *testing.T) { - r1 := NewRenderer(os.Stdout) - r1.SetHasDarkBackground(false) - if r1.HasDarkBackground() { - t.Error("Expected renderer to have light background") - } - r2 := NewRenderer(os.Stdout) - r2.SetHasDarkBackground(true) - if !r2.HasDarkBackground() { - t.Error("Expected renderer to have dark background") - } -} - -func TestRendererWithOutput(t *testing.T) { - f, err := os.Create(t.Name()) - if err != nil { - t.Fatal(err) - } - defer f.Close() - defer os.Remove(f.Name()) - r := NewRenderer(f) - r.SetColorProfile(termenv.TrueColor) - if r.ColorProfile() != termenv.TrueColor { - t.Error("Expected renderer to use true color") - } -} - -func TestRace(t *testing.T) { - r := NewRenderer(io.Discard) - o := r.Output() - - for i := 0; i < 100; i++ { - t.Run("SetColorProfile", func(t *testing.T) { - t.Parallel() - r.SetHasDarkBackground(false) - r.HasDarkBackground() - r.SetOutput(o) - r.SetColorProfile(termenv.ANSI256) - r.SetHasDarkBackground(true) - r.Output() - }) - } -} diff --git a/style_test.go b/style_test.go index 6ce4537..ede9ac9 100644 --- a/style_test.go +++ b/style_test.go @@ -1,16 +1,12 @@ package lipgloss import ( - "os" "reflect" "strings" "testing" ) func TestUnderline(t *testing.T) { - r := NewRenderer(io.Discard) - r.SetColorProfile(termenv.TrueColor) - r.SetHasDarkBackground(true) t.Parallel() tt := []struct { @@ -18,19 +14,19 @@ func TestUnderline(t *testing.T) { expected string }{ { - r.NewStyle().Underline(true), + NewStyle().Underline(true), "\x1b[4;4ma\x1b[0m\x1b[4;4mb\x1b[0m\x1b[4m \x1b[0m\x1b[4;4mc\x1b[0m", }, { - r.NewStyle().Underline(true).UnderlineSpaces(true), + NewStyle().Underline(true).UnderlineSpaces(true), "\x1b[4;4ma\x1b[0m\x1b[4;4mb\x1b[0m\x1b[4m \x1b[0m\x1b[4;4mc\x1b[0m", }, { - r.NewStyle().Underline(true).UnderlineSpaces(false), + NewStyle().Underline(true).UnderlineSpaces(false), "\x1b[4;4ma\x1b[0m\x1b[4;4mb\x1b[0m \x1b[4;4mc\x1b[0m", }, { - r.NewStyle().UnderlineSpaces(true), + NewStyle().UnderlineSpaces(true), "ab\x1b[4m \x1b[0mc", }, } @@ -47,9 +43,6 @@ func TestUnderline(t *testing.T) { } func TestStrikethrough(t *testing.T) { - r := NewRenderer(io.Discard) - r.SetColorProfile(termenv.TrueColor) - r.SetHasDarkBackground(true) t.Parallel() tt := []struct { @@ -57,19 +50,19 @@ func TestStrikethrough(t *testing.T) { expected string }{ { - r.NewStyle().Strikethrough(true), + NewStyle().Strikethrough(true), "\x1b[9ma\x1b[0m\x1b[9mb\x1b[0m\x1b[9m \x1b[0m\x1b[9mc\x1b[0m", }, { - r.NewStyle().Strikethrough(true).StrikethroughSpaces(true), + NewStyle().Strikethrough(true).StrikethroughSpaces(true), "\x1b[9ma\x1b[0m\x1b[9mb\x1b[0m\x1b[9m \x1b[0m\x1b[9mc\x1b[0m", }, { - r.NewStyle().Strikethrough(true).StrikethroughSpaces(false), + NewStyle().Strikethrough(true).StrikethroughSpaces(false), "\x1b[9ma\x1b[0m\x1b[9mb\x1b[0m \x1b[9mc\x1b[0m", }, { - r.NewStyle().StrikethroughSpaces(true), + NewStyle().StrikethroughSpaces(true), "ab\x1b[9m \x1b[0mc", }, } @@ -86,8 +79,6 @@ func TestStrikethrough(t *testing.T) { } func TestStyleRender(t *testing.T) { - r := NewRenderer(TrueColor, true) - r.SetHasDarkBackground(true) t.Parallel() tt := []struct { @@ -95,31 +86,31 @@ func TestStyleRender(t *testing.T) { expected string }{ { - r.NewStyle().Foreground(Color("#5A56E0")), + NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;2;89;86;224mhello\x1b[m", }, { - r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), + NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), "\x1b[38;2;89;86;224mhello\x1b[m", }, { - r.NewStyle().Bold(true), + NewStyle().Bold(true), "\x1b[1mhello\x1b[m", }, { - r.NewStyle().Italic(true), + NewStyle().Italic(true), "\x1b[3mhello\x1b[m", }, { - r.NewStyle().Underline(true), + NewStyle().Underline(true), "\x1b[4;4mh\x1b[m\x1b[4;4me\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4mo\x1b[m", }, { - r.NewStyle().Blink(true), + NewStyle().Blink(true), "\x1b[5mhello\x1b[m", }, { - r.NewStyle().Faint(true), + NewStyle().Faint(true), "\x1b[2mhello\x1b[m", }, } @@ -136,41 +127,40 @@ func TestStyleRender(t *testing.T) { } func TestStyleCustomRender(t *testing.T) { - r := NewRenderer(TrueColor, false) tt := []struct { style Style expected string }{ { - r.NewStyle().Foreground(Color("#5A56E0")), + NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;2;89;86;224mhello\x1b[m", }, { - r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), + NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), "\x1b[38;2;255;254;18mhello\x1b[m", }, { - r.NewStyle().Bold(true), + NewStyle().Bold(true), "\x1b[1mhello\x1b[m", }, { - r.NewStyle().Italic(true), + NewStyle().Italic(true), "\x1b[3mhello\x1b[m", }, { - r.NewStyle().Underline(true), + NewStyle().Underline(true), "\x1b[4;4mh\x1b[m\x1b[4;4me\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4ml\x1b[m\x1b[4;4mo\x1b[m", }, { - r.NewStyle().Blink(true), + NewStyle().Blink(true), "\x1b[5mhello\x1b[m", }, { - r.NewStyle().Faint(true), + NewStyle().Faint(true), "\x1b[2mhello\x1b[m", }, { - NewStyle().Faint(true).Renderer(r), + NewStyle().Faint(true), "\x1b[2mhello\x1b[m", }, } @@ -186,15 +176,6 @@ func TestStyleCustomRender(t *testing.T) { } } -func TestStyleRenderer(t *testing.T) { - r := NewRenderer(DetectColorProfile(os.Stdout, nil), true) - s1 := NewStyle().Bold(true) - s2 := s1.Renderer(r) - if s1.r == s2.r { - t.Fatalf("expected different renderers") - } -} - func TestValueCopy(t *testing.T) { t.Parallel()