mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 21:46:49 -04:00
Pick the built-in theme defaults for the terminal's background
Some theme colors can't have one default that suits both dark and light backgrounds. A gray background for the selected line of an inactive view has to be lighter than the terminal's background if that is dark, and darker if it is light. Give such fields a default for each kind of background. Derive it from the terminal's background color if the terminal tells us, so that it keeps the same distance from the background however dark or light that is, and takes on its tint. Otherwise, assume a black or white background. These defaults can't be the defaults of gui.theme. We would then have to tell whether a value there came from the user or from the built-in defaults, because only the user's value should win over a background default. Keep them apart from the user config instead, and leave these fields empty in the defaults of gui.theme, so that a value there always comes from the user. Tests ensure that no field has both kinds of defaults, and that both kinds of background set the same fields. Start with inactiveViewSelectedLineBgColor: the background mixed with 30% white if it is dark (#4d4d4d on black), or with 15% black if it is light (#d9d9d9 on white). A gray background shows where the selection is in a view without the focus more clearly than the bold text we used so far. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
34c86efc67
commit
ca950181e8
@@ -198,8 +198,8 @@ gui:
|
||||
- blue
|
||||
|
||||
# Background color of selected line when view doesn't have focus.
|
||||
inactiveViewSelectedLineBgColor:
|
||||
- bold
|
||||
# Default: a suitable RGB grey computed from the terminal's background color.
|
||||
inactiveViewSelectedLineBgColor: []
|
||||
|
||||
# Foreground color of copied commit
|
||||
cherryPickedCommitFgColor:
|
||||
|
||||
@@ -3,17 +3,61 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// ThemeForBackground returns gui.theme with the overrides for a dark or a light
|
||||
// background applied.
|
||||
func (c *GuiConfig) ThemeForBackground(lightBackground bool) ThemeConfig {
|
||||
// background applied, and with the defaults for that background in the fields
|
||||
// that neither of them sets. backgroundColor is the terminal's background color
|
||||
// as #rrggbb, or empty if we don't know it.
|
||||
func (c *GuiConfig) ThemeForBackground(lightBackground bool, backgroundColor string) ThemeConfig {
|
||||
override := lo.Ternary(lightBackground, c.LightTheme, c.DarkTheme)
|
||||
return mergeThemes(override, c.Theme)
|
||||
return mergeThemes(override, c.Theme, themeDefaults(lightBackground, backgroundColor))
|
||||
}
|
||||
|
||||
// themeDefaults returns the defaults of the theme fields whose default depends
|
||||
// on the terminal's background. GetDefaultConfig leaves these fields empty in
|
||||
// gui.theme, so that a value there comes from the user, and wins over these.
|
||||
//
|
||||
// They are derived from the background color, so that they keep the same
|
||||
// distance from it however dark or light it is. If we don't know the
|
||||
// background color, we assume black or white.
|
||||
func themeDefaults(lightBackground bool, backgroundColor string) ThemeConfig {
|
||||
if len(backgroundColor) != 7 || !utils.IsValidHexValue(backgroundColor) {
|
||||
backgroundColor = lo.Ternary(lightBackground, "#ffffff", "#000000")
|
||||
}
|
||||
|
||||
if lightBackground {
|
||||
return ThemeConfig{
|
||||
InactiveViewSelectedLineBgColor: []string{mixHexColors(backgroundColor, "#000000", 0.15)},
|
||||
}
|
||||
}
|
||||
|
||||
return ThemeConfig{
|
||||
InactiveViewSelectedLineBgColor: []string{mixHexColors(backgroundColor, "#ffffff", 0.3)},
|
||||
}
|
||||
}
|
||||
|
||||
// mixHexColors mixes two colors given as #rrggbb. amount is how much of b to
|
||||
// take: 0 gives a, 1 gives b.
|
||||
func mixHexColors(a, b string, amount float64) string {
|
||||
channel := func(color string, i int) float64 {
|
||||
value, _ := strconv.ParseUint(color[1+2*i:3+2*i], 16, 8)
|
||||
return float64(value)
|
||||
}
|
||||
|
||||
result := "#"
|
||||
for i := range 3 {
|
||||
mixed := channel(a, i) + (channel(b, i)-channel(a, i))*amount
|
||||
result += fmt.Sprintf("%02x", int(math.Round(mixed)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// mergeThemes takes each field from the first of the themes that sets it. For
|
||||
|
||||
@@ -31,13 +31,13 @@ func TestThemeForBackground(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
dark := gui.ThemeForBackground(false)
|
||||
dark := gui.ThemeForBackground(false, "")
|
||||
assert.Equal(t, []string{"yellow"}, dark.ActiveBorderColor)
|
||||
assert.Equal(t, []string{"default"}, dark.InactiveBorderColor)
|
||||
assert.Equal(t, map[string]string{"Alice": "red", "Bob": "blue"}, dark.AuthorColors)
|
||||
assert.Equal(t, gui.Theme.BranchColorPatterns, dark.BranchColorPatterns)
|
||||
|
||||
light := gui.ThemeForBackground(true)
|
||||
light := gui.ThemeForBackground(true, "")
|
||||
assert.Equal(t, []string{"green"}, light.ActiveBorderColor)
|
||||
assert.Equal(t, []string{"#777777"}, light.InactiveBorderColor)
|
||||
assert.Equal(t, map[string]string{"Alice": "red", "Bob": "#000080"}, light.AuthorColors)
|
||||
@@ -70,5 +70,54 @@ func TestEveryThemeFieldCanBeOverridden(t *testing.T) {
|
||||
|
||||
gui := GetDefaultConfig().Gui
|
||||
gui.DarkTheme = override
|
||||
assert.Equal(t, override, gui.ThemeForBackground(false))
|
||||
assert.Equal(t, override, gui.ThemeForBackground(false, ""))
|
||||
}
|
||||
|
||||
func TestThemeForBackgroundFallsBackToTheDefaultsForTheBackground(t *testing.T) {
|
||||
gui := GetDefaultConfig().Gui
|
||||
assert.Equal(t, []string{"#4d4d4d"}, gui.ThemeForBackground(false, "").InactiveViewSelectedLineBgColor)
|
||||
assert.Equal(t, []string{"#d9d9d9"}, gui.ThemeForBackground(true, "").InactiveViewSelectedLineBgColor)
|
||||
|
||||
gui.Theme.InactiveViewSelectedLineBgColor = []string{"bold"}
|
||||
assert.Equal(t, []string{"bold"}, gui.ThemeForBackground(false, "").InactiveViewSelectedLineBgColor)
|
||||
assert.Equal(t, []string{"bold"}, gui.ThemeForBackground(true, "").InactiveViewSelectedLineBgColor)
|
||||
|
||||
gui.LightTheme.InactiveViewSelectedLineBgColor = []string{"white"}
|
||||
assert.Equal(t, []string{"bold"}, gui.ThemeForBackground(false, "").InactiveViewSelectedLineBgColor)
|
||||
assert.Equal(t, []string{"white"}, gui.ThemeForBackground(true, "").InactiveViewSelectedLineBgColor)
|
||||
}
|
||||
|
||||
func TestBackgroundDefaultsAreDerivedFromTheBackgroundColor(t *testing.T) {
|
||||
gui := GetDefaultConfig().Gui
|
||||
assert.Equal(t, []string{"#626262"}, gui.ThemeForBackground(false, "#1e1e1e").InactiveViewSelectedLineBgColor)
|
||||
assert.Equal(t, []string{"#d7d1c1"}, gui.ThemeForBackground(true, "#fdf6e3").InactiveViewSelectedLineBgColor)
|
||||
}
|
||||
|
||||
// If gui.theme had a default for a field that also has a default for the
|
||||
// terminal's background, the former would always win.
|
||||
func TestFieldsWithBackgroundDefaultsHaveNoDefaultInGuiTheme(t *testing.T) {
|
||||
genericDefaults := setThemeFields(GetDefaultConfig().Gui.Theme)
|
||||
for _, field := range setThemeFields(themeDefaults(false, "")) {
|
||||
assert.NotContains(t, genericDefaults, field)
|
||||
}
|
||||
for _, field := range setThemeFields(themeDefaults(true, "")) {
|
||||
assert.NotContains(t, genericDefaults, field)
|
||||
}
|
||||
}
|
||||
|
||||
// If only one of the backgrounds had a default for a field, the field would
|
||||
// have no value at all with the other.
|
||||
func TestDarkAndLightDefaultsSetTheSameFields(t *testing.T) {
|
||||
assert.Equal(t, setThemeFields(themeDefaults(false, "")), setThemeFields(themeDefaults(true, "")))
|
||||
}
|
||||
|
||||
func setThemeFields(theme ThemeConfig) []string {
|
||||
var fields []string
|
||||
themeValue := reflect.ValueOf(theme)
|
||||
for i := range themeValue.NumField() {
|
||||
if themeValue.Field(i).Len() > 0 {
|
||||
fields = append(fields, themeValue.Type().Field(i).Name)
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
+12
-12
@@ -244,6 +244,7 @@ type ThemeConfig struct {
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"`
|
||||
// Background color of selected line when view doesn't have focus.
|
||||
// Default: a suitable RGB grey computed from the terminal's background color.
|
||||
InactiveViewSelectedLineBgColor []string `yaml:"inactiveViewSelectedLineBgColor" jsonschema:"minItems=1,uniqueItems=true"`
|
||||
// Foreground color of copied commit
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor" jsonschema:"minItems=1,uniqueItems=true"`
|
||||
@@ -890,18 +891,17 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
ColorScheme: "auto",
|
||||
Theme: ThemeConfig{
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
SearchingActiveBorderColor: []string{"cyan", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
InactiveViewSelectedLineBgColor: []string{"bold"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
MarkedBaseCommitBgColor: []string{"yellow"},
|
||||
MarkedBaseCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
ActiveBorderColor: []string{"green", "bold"},
|
||||
SearchingActiveBorderColor: []string{"cyan", "bold"},
|
||||
InactiveBorderColor: []string{"default"},
|
||||
OptionsTextColor: []string{"blue"},
|
||||
SelectedLineBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
MarkedBaseCommitBgColor: []string{"yellow"},
|
||||
MarkedBaseCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
},
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
|
||||
+12
-1
@@ -1252,7 +1252,7 @@ func (gui *Gui) showBreakingChangesMessage() {
|
||||
// applyTheme sets the colors of the app from the theme in the user config,
|
||||
// with the overrides for the terminal's background applied
|
||||
func (gui *Gui) applyTheme() {
|
||||
themeConfig := gui.UserConfig().Gui.ThemeForBackground(gui.terminalHasLightBackground())
|
||||
themeConfig := gui.UserConfig().Gui.ThemeForBackground(gui.terminalHasLightBackground(), gui.terminalBackgroundColor())
|
||||
theme.UpdateTheme(themeConfig)
|
||||
authors.SetCustomAuthors(themeConfig.AuthorColors)
|
||||
presentation.SetCustomBranches(themeConfig.BranchColorPatterns)
|
||||
@@ -1284,6 +1284,17 @@ func (gui *Gui) terminalHasLightBackground() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// terminalBackgroundColor returns the background color that the terminal told
|
||||
// us, as #rrggbb. It returns "" if the terminal didn't tell us, or if
|
||||
// gui.colorScheme disagrees with it about whether the background is light.
|
||||
func (gui *Gui) terminalBackgroundColor() string {
|
||||
detected := gui.g.DetectedColorScheme()
|
||||
if (detected.ColorScheme == gocui.ColorSchemeLight) != gui.terminalHasLightBackground() {
|
||||
return ""
|
||||
}
|
||||
return detected.Background
|
||||
}
|
||||
|
||||
func (gui *Gui) onUIThread(f func() error) {
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return f()
|
||||
|
||||
@@ -786,10 +786,7 @@
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "Background color of selected line when view doesn't have focus.",
|
||||
"default": [
|
||||
"bold"
|
||||
]
|
||||
"description": "Background color of selected line when view doesn't have focus.\nDefault: a suitable RGB grey computed from the terminal's background color."
|
||||
},
|
||||
"cherryPickedCommitFgColor": {
|
||||
"items": {
|
||||
@@ -929,7 +926,7 @@
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "Background color of selected line when view doesn't have focus."
|
||||
"description": "Background color of selected line when view doesn't have focus.\nDefault: a suitable RGB grey computed from the terminal's background color."
|
||||
},
|
||||
"cherryPickedCommitFgColor": {
|
||||
"items": {
|
||||
@@ -1051,7 +1048,7 @@
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "Background color of selected line when view doesn't have focus."
|
||||
"description": "Background color of selected line when view doesn't have focus.\nDefault: a suitable RGB grey computed from the terminal's background color."
|
||||
},
|
||||
"cherryPickedCommitFgColor": {
|
||||
"items": {
|
||||
|
||||
Reference in New Issue
Block a user