Fixed dark and light themes, ui.theme auto/dark/light
Colors no longer come from the terminal's palette, whose color 0 many color themes set to a grey that is not their background. A theme sets tview's default styles and the color names used in texts, all chosen from entries 16-255 of the 256-color table so terminals without 24-bit color show them exactly. "auto" picks light or dark from the background color the terminal reports. Settable in the Config view, applied at the next start (widgets copy their colors when created). Replaces using the terminal's own background and text colors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
@@ -2,7 +2,6 @@ package ui
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@@ -32,64 +31,66 @@ func TestParseTerminalAnswers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUseTerminalBackground(t *testing.T) {
|
||||
// tcell's terminal database also reads these from the real environment
|
||||
// (and keeps what it added to an entry because of them, so the test uses
|
||||
// a terminal nothing else looks up).
|
||||
t.Setenv("COLORTERM", "")
|
||||
t.Setenv("TCELL_TRUECOLOR", "")
|
||||
defer func(old tview.Theme, tab string) { tview.Styles, activeTabTag = old, tab }(tview.Styles, activeTabTag)
|
||||
env := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
|
||||
func TestChooseTheme(t *testing.T) {
|
||||
dark := terminalInfo{bg: color.RGBA{30, 30, 46, 255}, hasBG: true}
|
||||
light := terminalInfo{bg: color.RGBA{239, 241, 245, 255}, hasBG: true, fg: color.RGBA{76, 79, 105, 255}, hasFG: true}
|
||||
truecolor := env(map[string]string{"COLORTERM": "truecolor"})
|
||||
|
||||
light := terminalInfo{bg: color.RGBA{239, 241, 245, 255}, hasBG: true}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
info terminalInfo
|
||||
env func(string) string
|
||||
want bool
|
||||
setting string
|
||||
info terminalInfo
|
||||
want string
|
||||
}{
|
||||
{"dark background, true color", dark, truecolor, true},
|
||||
{"no true color", dark, env(map[string]string{"TERM": "vt100"}), false},
|
||||
{"ghostty without COLORTERM", dark, env(map[string]string{"TERM": "xterm-ghostty"}), true},
|
||||
{"true color disabled for tcell", dark, env(map[string]string{"TERM": "xterm-ghostty", "TCELL_TRUECOLOR": "disable"}), false},
|
||||
{"light background", light, truecolor, true},
|
||||
{"no answer", terminalInfo{}, truecolor, false},
|
||||
{"", light, "light"},
|
||||
{"auto", dark, "dark"},
|
||||
{"auto", terminalInfo{}, "dark"},
|
||||
{"dark", light, "dark"},
|
||||
{"light", dark, "light"},
|
||||
} {
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
||||
note := useTerminalBackground(tc.info, tc.env)
|
||||
if got := strings.Contains(note, "own color"); got != tc.want {
|
||||
t.Errorf("%s: %s", tc.name, note)
|
||||
}
|
||||
want := tcell.ColorBlack
|
||||
if tc.want {
|
||||
want = rgb(tc.info.bg)
|
||||
}
|
||||
if tview.Styles.PrimitiveBackgroundColor != want {
|
||||
t.Errorf("%s: background %v", tc.name, tview.Styles.PrimitiveBackgroundColor)
|
||||
if th, note := chooseTheme(tc.setting, tc.info); th.name != tc.want {
|
||||
t.Errorf("%q with %+v: %s (%s)", tc.setting, tc.info, th.name, note)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLightBackgroundUsesTheTextColor(t *testing.T) {
|
||||
defer func(old tview.Theme, tab string) { tview.Styles, activeTabTag = old, tab }(tview.Styles, activeTabTag)
|
||||
latte := terminalInfo{bg: color.RGBA{239, 241, 245, 255}, hasBG: true, fg: color.RGBA{76, 79, 105, 255}, hasFG: true}
|
||||
useTerminalBackground(latte, func(k string) string { return map[string]string{"COLORTERM": "truecolor"}[k] })
|
||||
st := tview.Styles
|
||||
if st.PrimaryTextColor != tcell.NewRGBColor(76, 79, 105) || st.BorderColor != st.PrimaryTextColor {
|
||||
t.Errorf("text %v, borders %v", st.PrimaryTextColor, st.BorderColor)
|
||||
func TestThemeApply(t *testing.T) {
|
||||
oldStyles, oldCurrent := tview.Styles, current
|
||||
oldNames := map[string]tcell.Color{}
|
||||
for k, v := range tcell.ColorNames {
|
||||
oldNames[k] = v
|
||||
}
|
||||
if st.ContrastBackgroundColor == tcell.ColorBlue {
|
||||
t.Error("input fields should not stay palette blue on a light background")
|
||||
defer func() {
|
||||
tview.Styles, current = oldStyles, oldCurrent
|
||||
for k, v := range oldNames {
|
||||
tcell.ColorNames[k] = v
|
||||
}
|
||||
}()
|
||||
|
||||
lightTheme.apply()
|
||||
if tview.Styles.PrimitiveBackgroundColor != lightTheme.bg || tview.Styles.PrimaryTextColor != lightTheme.text {
|
||||
t.Error("tview styles not set")
|
||||
}
|
||||
if activeTabTag != "[#eff1f5:#4c4f69]" {
|
||||
t.Errorf("active tab %s", activeTabTag)
|
||||
// Color names in texts mean the theme's colors.
|
||||
if named("yellow") != lightTheme.yellow || tcell.GetColor("black") != lightTheme.bg || tcell.GetColor("white") != lightTheme.text {
|
||||
t.Error("color names not set")
|
||||
}
|
||||
// Without a text color answer the text is black.
|
||||
tview.Styles = st
|
||||
useTerminalBackground(terminalInfo{bg: latte.bg, hasBG: true}, func(k string) string { return map[string]string{"COLORTERM": "truecolor"}[k] })
|
||||
if tview.Styles.PrimaryTextColor != tcell.NewRGBColor(0, 0, 0) {
|
||||
t.Errorf("fallback text %v", tview.Styles.PrimitiveBackgroundColor)
|
||||
if current.name != "light" {
|
||||
t.Error("current theme not set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestThemeColorsSurvive256Colors(t *testing.T) {
|
||||
// Every theme color is its own nearest match in the 256-color palette, so
|
||||
// a terminal without 24-bit color shows it exactly and never picks one of
|
||||
// the first 16 colors, which color themes change.
|
||||
palette := make([]tcell.Color, 256)
|
||||
for i := range palette {
|
||||
palette[i] = tcell.PaletteColor(i)
|
||||
}
|
||||
for _, th := range []theme{darkTheme, lightTheme} {
|
||||
for _, c := range []tcell.Color{th.bg, th.text, th.border, th.field, th.fieldMuted, th.list, th.pressed, th.toast,
|
||||
th.gray, th.yellow, th.green, th.red, th.blue, th.purple} {
|
||||
if got := tcell.FindColor(c, palette); got < tcell.PaletteColor(16) {
|
||||
t.Errorf("%s theme: %v maps to palette color %d", th.name, c, got-tcell.ColorValid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user