diff --git a/pkg/commands/patch/patch_parser.go b/pkg/commands/patch/patch_parser.go index 66e100ece..12426a3df 100644 --- a/pkg/commands/patch/patch_parser.go +++ b/pkg/commands/patch/patch_parser.go @@ -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) { diff --git a/pkg/gui/command_log_panel.go b/pkg/gui/command_log_panel.go index 6561f64cc..df3410da6 100644 --- a/pkg/gui/command_log_panel.go +++ b/pkg/gui/command_log_panel.go @@ -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)) } } diff --git a/pkg/gui/information_panel.go b/pkg/gui/information_panel.go index a58d2ddfb..d09f495c5 100644 --- a/pkg/gui/information_panel.go +++ b/pkg/gui/information_panel.go @@ -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() diff --git a/pkg/gui/mergeconflicts/rendering.go b/pkg/gui/mergeconflicts/rendering.go index 84708928f..67aa6c49d 100644 --- a/pkg/gui/mergeconflicts/rendering.go +++ b/pkg/gui/mergeconflicts/rendering.go @@ -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() } diff --git a/pkg/gui/modes.go b/pkg/gui/modes.go index 36843e998..af8977989 100644 --- a/pkg/gui/modes.go +++ b/pkg/gui/modes.go @@ -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(), diff --git a/pkg/gui/presentation/commits.go b/pkg/gui/presentation/commits.go index b9b9a622e..5823a7153 100644 --- a/pkg/gui/presentation/commits.go +++ b/pkg/gui/presentation/commits.go @@ -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 diff --git a/pkg/gui/presentation/reflog_commits.go b/pkg/gui/presentation/reflog_commits.go index 8c5d48779..b6b21e90f 100644 --- a/pkg/gui/presentation/reflog_commits.go +++ b/pkg/gui/presentation/reflog_commits.go @@ -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()) diff --git a/pkg/gui/style/basic.go b/pkg/gui/style/basic.go index 3e2c7a067..5cb1ec8aa 100644 --- a/pkg/gui/style/basic.go +++ b/pkg/gui/style/basic.go @@ -1,147 +1,98 @@ package style import ( - "fmt" - "github.com/gookit/color" ) -type BasicTextStyle struct { - fg color.Color - bg color.Color - opts []color.Color - - style color.Style +type TextStyle struct { + fg *Color + bg *Color + decoration Decoration } -func (b BasicTextStyle) Sprint(a ...interface{}) string { - return b.style.Sprint(a...) +type Sprinter interface { + Sprint(a ...interface{}) string + Sprintf(format string, a ...interface{}) string } -func (b BasicTextStyle) Sprintf(format string, a ...interface{}) string { - return b.style.Sprintf(format, a...) +func (b TextStyle) Sprint(a ...interface{}) string { + return b.deriveStyle().Sprint(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) - } +func (b TextStyle) Sprintf(format string, a ...interface{}) string { + return b.deriveStyle().Sprintf(format, a...) +} - if b.bg != 0 { - b.style = append(b.style, b.bg) - } - - b.style = append(b.style, b.opts...) +func (b TextStyle) SetBold() TextStyle { + b.decoration.SetBold() 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() - } +func (b TextStyle) SetUnderline() TextStyle { + b.decoration.SetUnderline() return b } -func (b BasicTextStyle) SetBold(v bool) TextStyle { - return b.setOpt(color.OpBold, v, true) +func (b TextStyle) SetReverse() TextStyle { + b.decoration.SetReverse() + return b } -func (b BasicTextStyle) SetReverse(v bool) TextStyle { - return b.setOpt(color.OpReverse, v, true) +func (b TextStyle) deriveStyle() Sprinter { + // TODO: consider caching + return deriveStyle(b.fg, b.bg, b.decoration) } -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, +func deriveStyle(fg *Color, bg *Color, decoration Decoration) Sprinter { + if fg == nil && bg == nil { + return color.Style(decoration.ToOpts()) } - 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) + isRgb := (fg != nil && fg.IsRGB()) || (bg != nil && bg.IsRGB()) + if isRgb { + s := &color.RGBStyle{} + if fg != nil { + s.SetFg(*fg.ToRGB().rgb) + } + if bg != nil { + s.SetBg(*bg.ToRGB().rgb) + } + s.SetOpts(decoration.ToOpts()) + return s } - res.style.SetOpts(b.opts) - return res + style := make([]color.Color, 0, 5) + + if fg != nil { + style = append(style, *fg.basic) + } + + if bg != nil { + style = append(style, *bg.basic) + } + + style = append(style, decoration.ToOpts()...) + + return color.Style(style) } -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() +// // 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) - for _, opt := range typedOther.opts { - bAsRGB.setOpt(opt, true) - } +func (b TextStyle) MergeStyle(other TextStyle) TextStyle { + b.decoration = b.decoration.Merge(other.decoration) - 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)) + if other.fg != nil { + b.fg = other.fg } + + if other.bg != nil { + b.bg = other.bg + } + + return b } diff --git a/pkg/gui/style/color.go b/pkg/gui/style/color.go new file mode 100644 index 000000000..ddd8549aa --- /dev/null +++ b/pkg/gui/style/color.go @@ -0,0 +1,35 @@ +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 + } + + rgb := c.basic.RGB() + c.rgb = &rgb + + return NewRGBColor(rgb) +} diff --git a/pkg/gui/style/decoration.go b/pkg/gui/style/decoration.go new file mode 100644 index 000000000..165a54084 --- /dev/null +++ b/pkg/gui/style/decoration.go @@ -0,0 +1,53 @@ +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 +} diff --git a/pkg/gui/style/rgb.go b/pkg/gui/style/rgb.go deleted file mode 100644 index bfda6a30b..000000000 --- a/pkg/gui/style/rgb.go +++ /dev/null @@ -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 -} diff --git a/pkg/gui/style/style.go b/pkg/gui/style/style.go index 50ea7b523..5911546c7 100644 --- a/pkg/gui/style/style.go +++ b/pkg/gui/style/style.go @@ -5,91 +5,90 @@ import ( "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) + // FgWhite = New(pointerTo(color.FgWhite), nil) + 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 = 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) + 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(0, 0).SetUnderline(true) - AttrBold = New(0, 0).SetUnderline(true) + AttrUnderline = New().SetUnderline() + AttrBold = New().SetBold() ) -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 New() TextStyle { + return TextStyle{} } -func SetConfigStyles(s TextStyle, keys []string, background bool) TextStyle { +func FromBasicFg(fg color.Color) TextStyle { + s := New() + c := NewBasicColor(fg) + s.fg = &c + return s +} + +func FromBasicBg(bg color.Color) TextStyle { + s := New() + c := NewBasicColor(bg) + s.bg = &c + return s +} + +var 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}, +} + +func SetConfigStyles(keys []string, background bool) TextStyle { + s := New() + 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) + 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 TextStyle + if background { + c = value.background + } else { + c = value.forground + } + s = s.MergeStyle(c) + } else if utils.IsValidHexValue(key) { + c := NewRGBColor(color.HEX(key, background)) + s.bg = &c } - 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 } } diff --git a/pkg/gui/style/style_test.go b/pkg/gui/style/style_test.go index ecb3efa70..669bd4db5 100644 --- a/pkg/gui/style/style_test.go +++ b/pkg/gui/style/style_test.go @@ -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{} diff --git a/pkg/theme/theme.go b/pkg/theme/theme.go index 0eafa84b2..ffccc606a 100644 --- a/pkg/theme/theme.go +++ b/pkg/theme/theme.go @@ -24,31 +24,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) + SelectedLineBgColor = style.SetConfigStyles(themeConfig.SelectedLineBgColor, true) + SelectedRangeBgColor = style.SetConfigStyles(themeConfig.SelectedRangeBgColor, true) GocuiSelectedLineBgColor = GetGocuiColor(themeConfig.SelectedLineBgColor) OptionsColor = GetGocuiColor(themeConfig.OptionsTextColor) - OptionsFgColor = style.SetConfigStyles(OptionsFgColor, themeConfig.OptionsTextColor, false) + OptionsFgColor = style.SetConfigStyles(themeConfig.OptionsTextColor, false) isLightTheme := themeConfig.LightTheme if isLightTheme { @@ -64,9 +64,9 @@ func UpdateTheme(themeConfig config.ThemeConfig) { // 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)) + if utils.IsValidHexValue(key) { + values := color.HEX(key).Values() + return gocui.NewRGBColor(int32(values[0]), int32(values[1]), int32(values[2])) } colorMap := map[string]gocui.Attribute{ diff --git a/pkg/utils/color.go b/pkg/utils/color.go index 9638b9fcc..0399377c7 100644 --- a/pkg/utils/color.go +++ b/pkg/utils/color.go @@ -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 }