1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-24 08:52:21 +02:00

Merge pull request #1409 from jesseduffield/jesse-switch-text-color-library

This commit is contained in:
Mark Kopenga 2021-07-31 11:02:16 +02:00 committed by GitHub
commit 3c78ba7ed3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 448 additions and 466 deletions

View File

@ -98,35 +98,35 @@ func (l *PatchLine) render(selected bool, included bool) string {
return coloredString(style.FgCyan, match[1], selected, included) + coloredString(theme.DefaultTextColor, match[2], selected, false)
}
colorAttr := theme.DefaultTextColor
textStyle := theme.DefaultTextColor
switch l.Kind {
case PATCH_HEADER:
colorAttr = colorAttr.SetBold(true)
textStyle = textStyle.SetBold()
case ADDITION:
colorAttr = colorAttr.SetColor(style.FgGreen)
textStyle = style.FgGreen
case DELETION:
colorAttr = colorAttr.SetColor(style.FgRed)
textStyle = style.FgRed
case COMMIT_SHA:
colorAttr = colorAttr.SetColor(style.FgYellow)
textStyle = style.FgYellow
}
return coloredString(colorAttr, content, selected, included)
return coloredString(textStyle, content, selected, included)
}
func coloredString(colorAttr style.TextStyle, str string, selected bool, included bool) string {
func coloredString(textStyle style.TextStyle, str string, selected bool, included bool) string {
if selected {
colorAttr = colorAttr.SetColor(theme.SelectedRangeBgColor)
textStyle = textStyle.MergeStyle(theme.SelectedRangeBgColor)
}
if len(str) < 2 {
return colorAttr.Sprint(str)
return textStyle.Sprint(str)
}
res := colorAttr.Sprint(str[:1])
res := textStyle.Sprint(str[:1])
if included {
return res + colorAttr.SetColor(style.BgGreen).Sprint(str[1:])
return res + textStyle.MergeStyle(style.BgGreen).Sprint(str[1:])
}
return res + colorAttr.Sprint(str[1:])
return res + textStyle.Sprint(str[1:])
}
func parsePatch(patch string) ([]int, []int, []*PatchLine) {

View File

@ -28,13 +28,13 @@ func (gui *Gui) GetOnRunCommand() func(entry oscommands.CmdLogEntry) {
currentSpan = entry.GetSpan()
}
clrAttr := theme.DefaultTextColor
textStyle := theme.DefaultTextColor
if !entry.GetCommandLine() {
clrAttr = clrAttr.SetColor(style.FgMagenta)
textStyle = style.FgMagenta
}
gui.CmdLog = append(gui.CmdLog, entry.GetCmdStr())
indentedCmdStr := " " + strings.Replace(entry.GetCmdStr(), "\n", "\n ", -1)
fmt.Fprint(gui.Views.Extras, "\n"+clrAttr.Sprint(indentedCmdStr))
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
}
}

View File

@ -15,8 +15,8 @@ func (gui *Gui) informationStr() string {
}
if gui.g.Mouse {
donate := style.FgMagenta.SetUnderline(true).Sprint(gui.Tr.Donate)
askQuestion := style.FgYellow.SetUnderline(true).Sprint(gui.Tr.AskQuestion)
donate := style.FgMagenta.SetUnderline().Sprint(gui.Tr.Donate)
askQuestion := style.FgYellow.SetUnderline().Sprint(gui.Tr.AskQuestion)
return fmt.Sprintf("%s %s %s", donate, askQuestion, gui.Config.GetVersion())
} else {
return gui.Config.GetVersion()

View File

@ -15,18 +15,18 @@ func ColoredConflictFile(content string, state *State, hasFocus bool) string {
conflict, remainingConflicts := shiftConflict(state.conflicts)
var outputBuffer bytes.Buffer
for i, line := range utils.SplitLines(content) {
colour := theme.DefaultTextColor
textStyle := theme.DefaultTextColor
if i == conflict.start || i == conflict.middle || i == conflict.end {
colour.SetColor(style.FgRed)
textStyle = style.FgRed
}
if hasFocus && state.conflictIndex < len(state.conflicts) && *state.conflicts[state.conflictIndex] == *conflict && shouldHighlightLine(i, conflict, state.conflictTop) {
colour = theme.SelectedRangeBgColor.SetBold(true)
textStyle = theme.SelectedRangeBgColor.SetBold()
}
if i == conflict.end && len(remainingConflicts) > 0 {
conflict, remainingConflicts = shiftConflict(remainingConflicts)
}
outputBuffer.WriteString(colour.Sprint(line) + "\n")
outputBuffer.WriteString(textStyle.Sprint(line) + "\n")
}
return outputBuffer.String()
}

View File

@ -27,7 +27,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
{
isActive: gui.GitCommand.PatchManager.Active,
description: func() string {
return style.FgYellow.SetBold(true).Sprintf(
return style.FgYellow.SetBold().Sprintf(
"%s %s",
gui.Tr.LcBuildingPatch,
style.AttrUnderline.Sprint(gui.Tr.ResetInParentheses),
@ -38,7 +38,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
{
isActive: gui.State.Modes.Filtering.Active,
description: func() string {
return style.FgRed.SetBold(true).Sprintf(
return style.FgRed.SetBold().Sprintf(
"%s '%s' %s",
gui.Tr.LcFilteringBy,
gui.State.Modes.Filtering.GetPath(),

View File

@ -10,6 +10,8 @@ import (
"github.com/kyokomi/emoji/v2"
)
var cherryPickedCommitTextStyle = style.FgCyan.MergeStyle(style.BgBlue)
func GetCommitListDisplayStrings(commits []*models.Commit, fullDescription bool, cherryPickedCommitShaMap map[string]bool, diffName string, parseEmoji bool) [][]string {
lines := make([][]string, len(commits))
@ -49,7 +51,7 @@ func getFullDescriptionDisplayStringsForCommit(c *models.Commit, cherryPickedCom
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
shaColor = style.FgCyan.SetColor(style.BgBlue)
shaColor = cherryPickedCommitTextStyle
}
tagString := ""
@ -57,7 +59,7 @@ func getFullDescriptionDisplayStringsForCommit(c *models.Commit, cherryPickedCom
if c.Action != "" {
secondColumnString = actionColorMap(c.Action).Sprint(c.Action)
} else if c.ExtraInfo != "" {
tagString = theme.DiffTerminalColor.SetBold(true).Sprint(c.ExtraInfo) + " "
tagString = theme.DiffTerminalColor.SetBold().Sprint(c.ExtraInfo) + " "
}
truncatedAuthor := utils.TruncateWithEllipsis(c.Author, 17)
@ -96,7 +98,7 @@ func getDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[s
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
shaColor = style.FgCyan.SetColor(style.BgBlue)
shaColor = cherryPickedCommitTextStyle
}
actionString := ""
@ -104,7 +106,7 @@ func getDisplayStringsForCommit(c *models.Commit, cherryPickedCommitShaMap map[s
if c.Action != "" {
actionString = actionColorMap(c.Action).Sprint(utils.WithPadding(c.Action, 7)) + " "
} else if len(c.Tags) > 0 {
tagString = theme.DiffTerminalColor.SetBold(true).Sprint(strings.Join(c.Tags, " ")) + " "
tagString = theme.DiffTerminalColor.SetBold().Sprint(strings.Join(c.Tags, " ")) + " "
}
name := c.Name

View File

@ -29,7 +29,7 @@ func GetReflogCommitListDisplayStrings(commits []*models.Commit, fullDescription
func coloredReflogSha(c *models.Commit, cherryPickedCommitShaMap map[string]bool) string {
shaColor := style.FgBlue
if cherryPickedCommitShaMap[c.Sha] {
shaColor = style.FgCyan.SetColor(style.BgBlue)
shaColor = cherryPickedCommitTextStyle
}
return shaColor.Sprint(c.ShortSha())

View File

@ -1,147 +0,0 @@
package style
import (
"fmt"
"github.com/gookit/color"
)
type BasicTextStyle struct {
fg color.Color
bg color.Color
opts []color.Color
style color.Style
}
func (b BasicTextStyle) Sprint(a ...interface{}) string {
return b.style.Sprint(a...)
}
func (b BasicTextStyle) Sprintf(format string, a ...interface{}) string {
return b.style.Sprintf(format, a...)
}
func (b BasicTextStyle) deriveStyle() BasicTextStyle {
// b.style[:0] makes sure to use the same slice memory
if b.fg == 0 {
// Fg is most of the time defined so we reverse the check
b.style = b.style[:0]
} else {
b.style = append(b.style[:0], b.fg)
}
if b.bg != 0 {
b.style = append(b.style, b.bg)
}
b.style = append(b.style, b.opts...)
return b
}
func (b BasicTextStyle) setOpt(opt color.Color, v bool, deriveIfChanged bool) BasicTextStyle {
if v {
// Add value
for _, listOpt := range b.opts {
if listOpt == opt {
// Option already added
return b
}
}
b.opts = append(b.opts, opt)
} else {
// Remove value
for idx, listOpt := range b.opts {
if listOpt == opt {
b.opts = append(b.opts[:idx], b.opts[idx+1:]...)
if deriveIfChanged {
return b.deriveStyle()
}
return b
}
}
}
if deriveIfChanged {
return b.deriveStyle()
}
return b
}
func (b BasicTextStyle) SetBold(v bool) TextStyle {
return b.setOpt(color.OpBold, v, true)
}
func (b BasicTextStyle) SetReverse(v bool) TextStyle {
return b.setOpt(color.OpReverse, v, true)
}
func (b BasicTextStyle) SetUnderline(v bool) TextStyle {
return b.setOpt(color.OpUnderscore, v, true)
}
func (b BasicTextStyle) SetRGBColor(red, green, blue uint8, background bool) TextStyle {
return b.convertToRGB().SetRGBColor(red, green, blue, background)
}
func (b BasicTextStyle) convertToRGB() RGBTextStyle {
res := RGBTextStyle{
fg: b.fg.RGB(),
fgSet: b.fg != 0,
opts: b.opts,
}
if b.bg != 0 {
// Need to convert bg to fg otherwise .RGB wont work
// for more info see https://github.com/gookit/color/issues/39
rgbBg := (b.bg - 10).RGB()
rgbBg[3] = 1
res.bg = &rgbBg
res.style = *color.NewRGBStyle(res.fg, rgbBg)
} else {
res.style = *color.NewRGBStyle(res.fg)
}
res.style.SetOpts(b.opts)
return res
}
func (b BasicTextStyle) SetColor(other TextStyle) TextStyle {
switch typedOther := other.(type) {
case BasicTextStyle:
if typedOther.fg != 0 {
b.fg = typedOther.fg
}
if typedOther.bg != 0 {
b.bg = typedOther.bg
}
for _, opt := range typedOther.opts {
b = b.setOpt(opt, true, false)
}
return b.deriveStyle()
case RGBTextStyle:
bAsRGB := b.convertToRGB()
for _, opt := range typedOther.opts {
bAsRGB.setOpt(opt, true)
}
if typedOther.fgSet {
bAsRGB.fg = typedOther.fg
bAsRGB.style.SetFg(typedOther.fg)
}
if typedOther.bg != nil {
// Making sure to copy the value
bAsRGB.bg = &color.RGBColor{}
*bAsRGB.bg = *typedOther.bg
bAsRGB.style.SetBg(*typedOther.bg)
}
return bAsRGB
default:
panic(fmt.Sprintf("got %T but expected BasicTextStyle or RGBTextStyle", typedOther))
}
}

View File

@ -0,0 +1,38 @@
package style
import (
"github.com/gookit/color"
)
var (
FgWhite = FromBasicFg(color.FgWhite)
FgLightWhite = FromBasicFg(color.FgLightWhite)
FgBlack = FromBasicFg(color.FgBlack)
FgBlackLighter = FromBasicFg(color.FgBlack.Light())
FgCyan = FromBasicFg(color.FgCyan)
FgRed = FromBasicFg(color.FgRed)
FgGreen = FromBasicFg(color.FgGreen)
FgBlue = FromBasicFg(color.FgBlue)
FgYellow = FromBasicFg(color.FgYellow)
FgMagenta = FromBasicFg(color.FgMagenta)
BgWhite = FromBasicBg(color.BgWhite)
BgBlack = FromBasicBg(color.BgBlack)
BgRed = FromBasicBg(color.BgRed)
BgGreen = FromBasicBg(color.BgGreen)
BgYellow = FromBasicBg(color.BgYellow)
BgBlue = FromBasicBg(color.BgBlue)
BgMagenta = FromBasicBg(color.BgMagenta)
BgCyan = FromBasicBg(color.BgCyan)
AttrUnderline = New().SetUnderline()
AttrBold = New().SetBold()
)
func FromBasicFg(fg color.Color) TextStyle {
return New().SetFg(NewBasicColor(fg))
}
func FromBasicBg(bg color.Color) TextStyle {
return New().SetBg(NewBasicColor(bg))
}

32
pkg/gui/style/color.go Normal file
View File

@ -0,0 +1,32 @@
package style
import "github.com/gookit/color"
type Color struct {
rgb *color.RGBColor
basic *color.Color
}
func NewRGBColor(cl color.RGBColor) Color {
c := Color{}
c.rgb = &cl
return c
}
func NewBasicColor(cl color.Color) Color {
c := Color{}
c.basic = &cl
return c
}
func (c Color) IsRGB() bool {
return c.rgb != nil
}
func (c Color) ToRGB() Color {
if c.IsRGB() {
return c
}
return NewRGBColor(c.basic.RGB())
}

View File

@ -0,0 +1,55 @@
package style
import "github.com/gookit/color"
type Decoration struct {
bold bool
underline bool
reverse bool
}
func (d Decoration) SetBold() {
d.bold = true
}
func (d Decoration) SetUnderline() {
d.underline = true
}
func (d Decoration) SetReverse() {
d.reverse = true
}
func (d Decoration) ToOpts() color.Opts {
opts := make([]color.Color, 0, 3)
if d.bold {
opts = append(opts, color.OpBold)
}
if d.underline {
opts = append(opts, color.OpUnderscore)
}
if d.reverse {
opts = append(opts, color.OpReverse)
}
return opts
}
func (d Decoration) Merge(other Decoration) Decoration {
if other.bold {
d.bold = true
}
if other.underline {
d.underline = true
}
if other.reverse {
d.reverse = true
}
return d
}

View File

@ -1,111 +0,0 @@
package style
import (
"fmt"
"github.com/gookit/color"
)
type RGBTextStyle struct {
opts color.Opts
fgSet bool
fg color.RGBColor
bg *color.RGBColor
style color.RGBStyle
}
func (b RGBTextStyle) Sprint(a ...interface{}) string {
return b.style.Sprint(a...)
}
func (b RGBTextStyle) Sprintf(format string, a ...interface{}) string {
return b.style.Sprintf(format, a...)
}
func (b RGBTextStyle) setOpt(opt color.Color, v bool) RGBTextStyle {
if v {
// Add value
for _, listOpt := range b.opts {
if listOpt == opt {
return b
}
}
b.opts = append(b.opts, opt)
} else {
// Remove value
for idx, listOpt := range b.opts {
if listOpt == opt {
b.opts = append(b.opts[:idx], b.opts[idx+1:]...)
return b
}
}
}
return b
}
func (b RGBTextStyle) SetBold(v bool) TextStyle {
b = b.setOpt(color.OpBold, v)
b.style.SetOpts(b.opts)
return b
}
func (b RGBTextStyle) SetReverse(v bool) TextStyle {
b = b.setOpt(color.OpReverse, v)
b.style.SetOpts(b.opts)
return b
}
func (b RGBTextStyle) SetUnderline(v bool) TextStyle {
b = b.setOpt(color.OpUnderscore, v)
b.style.SetOpts(b.opts)
return b
}
func (b RGBTextStyle) SetColor(style TextStyle) TextStyle {
var rgbStyle RGBTextStyle
switch typedStyle := style.(type) {
case BasicTextStyle:
rgbStyle = typedStyle.convertToRGB()
case RGBTextStyle:
rgbStyle = typedStyle
default:
panic(fmt.Sprintf("got %T but expected BasicTextStyle or RGBTextStyle", typedStyle))
}
for _, opt := range rgbStyle.GetOpts() {
b = b.setOpt(opt, true)
}
if rgbStyle.fgSet {
b.fg = rgbStyle.fg
b.style.SetFg(rgbStyle.fg)
b.fgSet = true
}
if rgbStyle.bg != nil {
// Making sure to copy value
b.bg = &color.RGBColor{}
*b.bg = *rgbStyle.bg
b.style.SetBg(*rgbStyle.bg)
}
return b
}
func (b RGBTextStyle) SetRGBColor(red, green, blue uint8, background bool) TextStyle {
parsedColor := color.Rgb(red, green, blue, background)
if background {
b.bg = &parsedColor
b.style.SetBg(parsedColor)
} else {
b.fg = parsedColor
b.style.SetFg(parsedColor)
b.fgSet = true
}
return b
}
func (b RGBTextStyle) GetOpts() color.Opts {
return b.opts
}

View File

@ -1,97 +0,0 @@
package style
import (
"github.com/gookit/color"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type TextStyle interface {
Sprint(a ...interface{}) string
Sprintf(format string, a ...interface{}) string
SetBold(v bool) TextStyle
SetReverse(v bool) TextStyle
SetUnderline(v bool) TextStyle
SetColor(style TextStyle) TextStyle
SetRGBColor(r, g, b uint8, background bool) TextStyle
}
var (
FgWhite = New(color.FgWhite, 0)
FgLightWhite = New(color.FgLightWhite, 0)
FgBlack = New(color.FgBlack, 0)
FgBlackLighter = New(color.FgBlack.Light(), 0)
FgCyan = New(color.FgCyan, 0)
FgRed = New(color.FgRed, 0)
FgGreen = New(color.FgGreen, 0)
FgBlue = New(color.FgBlue, 0)
FgYellow = New(color.FgYellow, 0)
FgMagenta = New(color.FgMagenta, 0)
BgWhite = New(0, color.BgWhite)
BgBlack = New(0, color.BgBlack)
BgRed = New(0, color.BgRed)
BgGreen = New(0, color.BgGreen)
BgYellow = New(0, color.BgYellow)
BgBlue = New(0, color.BgBlue)
BgMagenta = New(0, color.BgMagenta)
BgCyan = New(0, color.BgCyan)
AttrUnderline = New(0, 0).SetUnderline(true)
AttrBold = New(0, 0).SetUnderline(true)
)
func New(fg color.Color, bg color.Color, opts ...color.Color) TextStyle {
return BasicTextStyle{
fg: fg,
bg: bg,
opts: opts,
style: color.Style{},
}.deriveStyle()
}
func SetConfigStyles(s TextStyle, keys []string, background bool) TextStyle {
for _, key := range keys {
colorMap := map[string]struct {
forground TextStyle
background TextStyle
}{
"default": {FgWhite, BgBlack},
"black": {FgBlack, BgBlack},
"red": {FgRed, BgRed},
"green": {FgGreen, BgGreen},
"yellow": {FgYellow, BgYellow},
"blue": {FgBlue, BgBlue},
"magenta": {FgMagenta, BgMagenta},
"cyan": {FgCyan, BgCyan},
"white": {FgWhite, BgWhite},
}
value, present := colorMap[key]
if present {
if background {
s = s.SetColor(value.background)
} else {
s = s.SetColor(value.forground)
}
continue
}
if key == "bold" {
s = s.SetBold(true)
continue
} else if key == "reverse" {
s = s.SetReverse(true)
continue
} else if key == "underline" {
s = s.SetUnderline(true)
continue
}
r, g, b, validHexColor := utils.GetHexColorValues(key)
if validHexColor {
s = s.SetRGBColor(r, g, b, background)
continue
}
}
return s
}

View File

@ -53,39 +53,39 @@ func TestNewStyle(t *testing.T) {
func TestBasicSetColor(t *testing.T) {
type scenario struct {
name string
colorToSet BasicTextStyle
expect BasicTextStyle
colorToSet TextStyle
expect TextStyle
}
scenarios := []scenario{
{
"empty color",
BasicTextStyle{},
BasicTextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}}},
TextStyle{},
TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}}},
{
"set new fg color",
BasicTextStyle{fg: color.FgCyan},
BasicTextStyle{fg: color.FgCyan, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
TextStyle{fg: color.FgCyan},
TextStyle{fg: color.FgCyan, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
},
{
"set new bg color",
BasicTextStyle{bg: color.BgGray},
BasicTextStyle{fg: color.FgRed, bg: color.BgGray, opts: []color.Color{color.OpBold}},
TextStyle{bg: color.BgGray},
TextStyle{fg: color.FgRed, bg: color.BgGray, opts: []color.Color{color.OpBold}},
},
{
"set new fg and bg color",
BasicTextStyle{fg: color.FgCyan, bg: color.BgGray},
BasicTextStyle{fg: color.FgCyan, bg: color.BgGray, opts: []color.Color{color.OpBold}},
TextStyle{fg: color.FgCyan, bg: color.BgGray},
TextStyle{fg: color.FgCyan, bg: color.BgGray, opts: []color.Color{color.OpBold}},
},
{
"add options",
BasicTextStyle{opts: []color.Color{color.OpUnderscore}},
BasicTextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold, color.OpUnderscore}},
TextStyle{opts: []color.Color{color.OpUnderscore}},
TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold, color.OpUnderscore}},
},
{
"add options that already exists",
BasicTextStyle{opts: []color.Color{color.OpBold}},
BasicTextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
TextStyle{opts: []color.Color{color.OpBold}},
TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
},
}
@ -127,12 +127,12 @@ func TestRGBSetColor(t *testing.T) {
},
{
"empty BasicTextStyle input",
BasicTextStyle{},
TextStyle{},
RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold}},
},
{
"set fg and bg color using BasicTextStyle",
BasicTextStyle{fg: color.FgCyan, bg: color.BgGray},
TextStyle{fg: color.FgCyan, bg: color.BgGray},
RGBTextStyle{fgSet: true, fg: cyan, bg: toBg(gray), opts: []color.Color{color.OpBold}},
},
{
@ -147,7 +147,7 @@ func TestRGBSetColor(t *testing.T) {
},
{
"add options using BasicTextStyle",
BasicTextStyle{opts: []color.Color{color.OpUnderscore}},
TextStyle{opts: []color.Color{color.OpUnderscore}},
RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold, color.OpUnderscore}},
},
{
@ -159,10 +159,10 @@ func TestRGBSetColor(t *testing.T) {
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
style, ok := New(color.FgRed, color.BgBlue).SetBold(true).(BasicTextStyle)
style, ok := New(color.FgRed, color.BgBlue).SetBold().(BasicTextStyle)
assert.True(t, ok, "SetBold should return a interface of type BasicTextStyle")
rgbStyle, ok := style.convertToRGB().SetColor(s.colorToSet).(RGBTextStyle)
rgbStyle, ok := style.convertToRGB().MergeStyle(s.colorToSet).(RGBTextStyle)
assert.True(t, ok, "SetColor should return a interface of type RGBTextColor")
rgbStyle.style = color.RGBStyle{}

147
pkg/gui/style/text_style.go Normal file
View File

@ -0,0 +1,147 @@
package style
import (
"github.com/gookit/color"
)
// A TextStyle contains a foreground color, background color, and
// decorations (bold/underline/reverse).
//
// Colors may each be either 16-bit or 256-bit RGB colors. When
// we need to produce a string with a TextStyle, if either foreground or
// background color is RGB, we'll promote the other color component to RGB as well.
// We could simplify this code by forcing everything to be RGB, but we're not
// sure how compatible or efficient that would be with various terminals.
// Lazygit will typically stick to 16-bit colors, but users may configure RGB colors.
//
// TextStyles are value objects, not entities, so for example if you want to
// add the bold decoration to a TextStyle, we'll create a new TextStyle with
// that decoration applied.
//
// Decorations are additive, so when we merge two TextStyles, if either is bold
// then the resulting style will also be bold.
//
// So that we aren't rederiving the underlying style each time we want to print
// a string, we derive it when a new TextStyle is created and store it in the
// `style` field.
type TextStyle struct {
fg *Color
bg *Color
decoration Decoration
style Sprinter
}
type Sprinter interface {
Sprint(a ...interface{}) string
Sprintf(format string, a ...interface{}) string
}
func New() TextStyle {
s := TextStyle{}
s.style = s.deriveStyle()
return s
}
func (b TextStyle) Sprint(a ...interface{}) string {
return b.style.Sprint(a...)
}
func (b TextStyle) Sprintf(format string, a ...interface{}) string {
return b.style.Sprintf(format, a...)
}
// note that our receiver here is not a pointer which means we're receiving a
// copy of the original TextStyle. This allows us to mutate and return that
// TextStyle receiver without actually modifying the original.
func (b TextStyle) SetBold() TextStyle {
b.decoration.SetBold()
b.style = b.deriveStyle()
return b
}
func (b TextStyle) SetUnderline() TextStyle {
b.decoration.SetUnderline()
b.style = b.deriveStyle()
return b
}
func (b TextStyle) SetReverse() TextStyle {
b.decoration.SetReverse()
b.style = b.deriveStyle()
return b
}
func (b TextStyle) SetBg(color Color) TextStyle {
b.bg = &color
b.style = b.deriveStyle()
return b
}
func (b TextStyle) SetFg(color Color) TextStyle {
b.fg = &color
b.style = b.deriveStyle()
return b
}
func (b TextStyle) MergeStyle(other TextStyle) TextStyle {
b.decoration = b.decoration.Merge(other.decoration)
if other.fg != nil {
b.fg = other.fg
}
if other.bg != nil {
b.bg = other.bg
}
b.style = b.deriveStyle()
return b
}
func (b TextStyle) deriveStyle() Sprinter {
if b.fg == nil && b.bg == nil {
return color.Style(b.decoration.ToOpts())
}
isRgb := (b.fg != nil && b.fg.IsRGB()) || (b.bg != nil && b.bg.IsRGB())
if isRgb {
return b.deriveRGBStyle()
}
return b.deriveBasicStyle()
}
func (b TextStyle) deriveBasicStyle() color.Style {
style := make([]color.Color, 0, 5)
if b.fg != nil {
style = append(style, *b.fg.basic)
}
if b.bg != nil {
style = append(style, *b.bg.basic)
}
style = append(style, b.decoration.ToOpts()...)
return color.Style(style)
}
func (b TextStyle) deriveRGBStyle() *color.RGBStyle {
style := &color.RGBStyle{}
if b.fg != nil {
style.SetFg(*b.fg.ToRGB().rgb)
}
if b.bg != nil {
style.SetBg(*b.bg.ToRGB().rgb)
}
style.SetOpts(b.decoration.ToOpts())
return style
}

45
pkg/theme/gocui.go Normal file
View File

@ -0,0 +1,45 @@
package theme
import (
"github.com/gookit/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/utils"
)
var gocuiColorMap = map[string]gocui.Attribute{
"default": gocui.ColorDefault,
"black": gocui.ColorBlack,
"red": gocui.ColorRed,
"green": gocui.ColorGreen,
"yellow": gocui.ColorYellow,
"blue": gocui.ColorBlue,
"magenta": gocui.ColorMagenta,
"cyan": gocui.ColorCyan,
"white": gocui.ColorWhite,
"bold": gocui.AttrBold,
"reverse": gocui.AttrReverse,
"underline": gocui.AttrUnderline,
}
// GetAttribute gets the gocui color attribute from the string
func GetGocuiAttribute(key string) gocui.Attribute {
if utils.IsValidHexValue(key) {
values := color.HEX(key).Values()
return gocui.NewRGBColor(int32(values[0]), int32(values[1]), int32(values[2]))
}
value, present := gocuiColorMap[key]
if present {
return value
}
return gocui.ColorWhite
}
// GetGocuiStyle bitwise OR's a list of attributes obtained via the given keys
func GetGocuiStyle(keys []string) gocui.Attribute {
var attribute gocui.Attribute
for _, key := range keys {
attribute |= GetGocuiAttribute(key)
}
return attribute
}

57
pkg/theme/style.go Normal file
View File

@ -0,0 +1,57 @@
package theme
import (
"github.com/gookit/color"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
)
var colorMap = map[string]struct {
foreground style.TextStyle
background style.TextStyle
}{
"default": {style.FgWhite, style.BgBlack},
"black": {style.FgBlack, style.BgBlack},
"red": {style.FgRed, style.BgRed},
"green": {style.FgGreen, style.BgGreen},
"yellow": {style.FgYellow, style.BgYellow},
"blue": {style.FgBlue, style.BgBlue},
"magenta": {style.FgMagenta, style.BgMagenta},
"cyan": {style.FgCyan, style.BgCyan},
"white": {style.FgWhite, style.BgWhite},
}
func GetTextStyle(keys []string, background bool) style.TextStyle {
s := style.New()
for _, key := range keys {
switch key {
case "bold":
s = s.SetBold()
case "reverse":
s = s.SetReverse()
case "underline":
s = s.SetUnderline()
default:
value, present := colorMap[key]
if present {
var c style.TextStyle
if background {
c = value.background
} else {
c = value.foreground
}
s = s.MergeStyle(c)
} else if utils.IsValidHexValue(key) {
c := style.NewRGBColor(color.HEX(key, background))
if background {
s.SetBg(c)
} else {
s.SetFg(c)
}
}
}
}
return s
}

View File

@ -1,11 +1,9 @@
package theme
import (
"github.com/gookit/color"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/utils"
)
var (
@ -24,31 +22,31 @@ var (
OptionsColor gocui.Attribute
// DefaultTextColor is the default text color
DefaultTextColor = style.New(color.FgWhite, 0)
DefaultTextColor = style.FgWhite
// DefaultHiTextColor is the default highlighted text color
DefaultHiTextColor = style.New(color.FgLightWhite, 0)
DefaultHiTextColor = style.FgLightWhite
// SelectedLineBgColor is the background color for the selected line
SelectedLineBgColor = style.New(0, 0)
SelectedLineBgColor = style.New()
// SelectedRangeBgColor is the background color of the selected range of lines
SelectedRangeBgColor = style.New(0, 0)
SelectedRangeBgColor = style.New()
OptionsFgColor = style.New(0, 0)
OptionsFgColor = style.New()
DiffTerminalColor = style.New(color.FgMagenta, 0)
DiffTerminalColor = style.FgMagenta
)
// UpdateTheme updates all theme variables
func UpdateTheme(themeConfig config.ThemeConfig) {
ActiveBorderColor = GetGocuiColor(themeConfig.ActiveBorderColor)
InactiveBorderColor = GetGocuiColor(themeConfig.InactiveBorderColor)
SelectedLineBgColor = style.SetConfigStyles(SelectedLineBgColor, themeConfig.SelectedLineBgColor, true)
SelectedRangeBgColor = style.SetConfigStyles(SelectedRangeBgColor, themeConfig.SelectedRangeBgColor, true)
GocuiSelectedLineBgColor = GetGocuiColor(themeConfig.SelectedLineBgColor)
OptionsColor = GetGocuiColor(themeConfig.OptionsTextColor)
OptionsFgColor = style.SetConfigStyles(OptionsFgColor, themeConfig.OptionsTextColor, false)
ActiveBorderColor = GetGocuiStyle(themeConfig.ActiveBorderColor)
InactiveBorderColor = GetGocuiStyle(themeConfig.InactiveBorderColor)
SelectedLineBgColor = GetTextStyle(themeConfig.SelectedLineBgColor, true)
SelectedRangeBgColor = GetTextStyle(themeConfig.SelectedRangeBgColor, true)
GocuiSelectedLineBgColor = GetGocuiStyle(themeConfig.SelectedLineBgColor)
OptionsColor = GetGocuiStyle(themeConfig.OptionsTextColor)
OptionsFgColor = GetTextStyle(themeConfig.OptionsTextColor, false)
isLightTheme := themeConfig.LightTheme
if isLightTheme {
@ -61,40 +59,3 @@ func UpdateTheme(themeConfig config.ThemeConfig) {
GocuiDefaultTextColor = gocui.ColorWhite
}
}
// GetAttribute gets the gocui color attribute from the string
func GetGocuiAttribute(key string) gocui.Attribute {
r, g, b, validHexColor := utils.GetHexColorValues(key)
if validHexColor {
return gocui.NewRGBColor(int32(r), int32(g), int32(b))
}
colorMap := map[string]gocui.Attribute{
"default": gocui.ColorDefault,
"black": gocui.ColorBlack,
"red": gocui.ColorRed,
"green": gocui.ColorGreen,
"yellow": gocui.ColorYellow,
"blue": gocui.ColorBlue,
"magenta": gocui.ColorMagenta,
"cyan": gocui.ColorCyan,
"white": gocui.ColorWhite,
"bold": gocui.AttrBold,
"reverse": gocui.AttrReverse,
"underline": gocui.AttrUnderline,
}
value, present := colorMap[key]
if present {
return value
}
return gocui.ColorWhite
}
// GetGocuiColor bitwise OR's a list of attributes obtained via the given keys
func GetGocuiColor(keys []string) gocui.Attribute {
var attribute gocui.Attribute
for _, key := range keys {
attribute |= GetGocuiAttribute(key)
}
return attribute
}

View File

@ -1,7 +1,6 @@
package utils
import (
"encoding/hex"
"regexp"
)
@ -33,22 +32,23 @@ func getPadWidths(stringArrays [][]string) []int {
return padWidths
}
// GetHexColorValues returns the rgb values of a hex color
func GetHexColorValues(v string) (r uint8, g uint8, b uint8, valid bool) {
if len(v) == 4 {
v = string([]byte{v[0], v[1], v[1], v[2], v[2], v[3], v[3]})
} else if len(v) != 7 {
return
func IsValidHexValue(v string) bool {
if len(v) != 4 && len(v) != 7 {
return false
}
if v[0] != '#' {
return
return false
}
rgb, err := hex.DecodeString(v[1:])
if err != nil {
return
for _, char := range v[1:] {
switch char {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
continue
default:
return false
}
}
return rgb[0], rgb[1], rgb[2], true
return true
}