From 86459f9a2f15c85af606745a1c11a1baa132b679 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Mon, 9 Jan 2023 20:10:55 +0100 Subject: [PATCH] feat: cycle through colors resolves #3327 --- src/ansi/ansi_writer.go | 22 +++---- src/ansi/ansi_writer_test.go | 88 +++++++++++++-------------- src/ansi/colors.go | 28 ++++----- src/ansi/colors_test.go | 2 +- src/ansi/colors_unix.go | 6 +- src/ansi/colors_windows.go | 8 +-- src/ansi/cycle.go | 10 +++ src/engine/block.go | 9 ++- src/engine/config.go | 3 +- src/engine/engine.go | 13 ++-- src/engine/segment.go | 33 ++++++---- themes/schema.json | 16 +++++ website/docs/configuration/colors.mdx | 26 ++++++++ 13 files changed, 168 insertions(+), 96 deletions(-) create mode 100644 src/ansi/cycle.go diff --git a/src/ansi/ansi_writer.go b/src/ansi/ansi_writer.go index c33e3208..8d724c06 100644 --- a/src/ansi/ansi_writer.go +++ b/src/ansi/ansi_writer.go @@ -34,9 +34,9 @@ type style struct { End string } -type cachedColor struct { - Background string - Foreground string +type Colors struct { + Background string `json:"background"` + Foreground string `json:"foreground"` } const ( @@ -75,9 +75,9 @@ const ( // Writer writes colorized ANSI strings type Writer struct { TerminalBackground string - Colors *cachedColor - ParentColors []*cachedColor - AnsiColors Colors + Colors *Colors + ParentColors []*Colors + AnsiColors ColorString Plain bool builder strings.Builder @@ -175,7 +175,7 @@ func (w *Writer) Init(shellName string) { } func (w *Writer) SetColors(background, foreground string) { - w.Colors = &cachedColor{ + w.Colors = &Colors{ Background: background, Foreground: foreground, } @@ -183,9 +183,9 @@ func (w *Writer) SetColors(background, foreground string) { func (w *Writer) SetParentColors(background, foreground string) { if w.ParentColors == nil { - w.ParentColors = make([]*cachedColor, 0) + w.ParentColors = make([]*Colors, 0) } - w.ParentColors = append([]*cachedColor{{ + w.ParentColors = append([]*Colors{{ Background: background, Foreground: foreground, }}, w.ParentColors...) @@ -279,7 +279,7 @@ func (w *Writer) Write(background, foreground, text string) { w.background, w.foreground = w.asAnsiColors(background, foreground) // default to white foreground if w.foreground.IsEmpty() { - w.foreground = w.AnsiColors.AnsiColorFromString("white", false) + w.foreground = w.AnsiColors.ToColor("white", false) } // validate if we start with a color override match := regex.FindNamedRegexMatch(anchorRegex, text) @@ -361,7 +361,7 @@ func (w *Writer) writeEscapedAnsiString(text string) { } func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color { - return w.AnsiColors.AnsiColorFromString(colorString, isBackground) + return w.AnsiColors.ToColor(colorString, isBackground) } func (w *Writer) writeSegmentColors() { diff --git a/src/ansi/ansi_writer_test.go b/src/ansi/ansi_writer_test.go index ce171cd8..9d6592a8 100644 --- a/src/ansi/ansi_writer_test.go +++ b/src/ansi/ansi_writer_test.go @@ -13,187 +13,187 @@ func TestWriteANSIColors(t *testing.T) { Case string Expected string Input string - Colors *cachedColor - Parent *cachedColor + Colors *Colors + Parent *Colors TerminalBackground string }{ { Case: "Bold", Input: "test", Expected: "\x1b[1m\x1b[30mtest\x1b[22m\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override", Input: "<#ffffff>test", Expected: "\x1b[1m\x1b[30m\x1b[38;2;255;255;255mtest\x1b[0m\x1b[30m\x1b[22m\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override, flavor 2", Input: "<#ffffff>test", Expected: "\x1b[38;2;255;255;255m\x1b[1mtest\x1b[22m\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Double override", Input: "<#ffffff>jan@<#ffffff>Jans-MBP", Expected: "\x1b[48;2;255;87;51m\x1b[38;2;255;255;255mjan\x1b[32m@\x1b[38;2;255;255;255mJans-MBP\x1b[0m", - Colors: &cachedColor{Foreground: "green", Background: "#FF5733"}, + Colors: &Colors{Foreground: "green", Background: "#FF5733"}, }, { Case: "No color override", Input: "test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Inherit foreground", Input: "test", Expected: "\x1b[47m\x1b[33mtest\x1b[0m", - Colors: &cachedColor{Foreground: ParentForeground, Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "white"}, + Colors: &Colors{Foreground: ParentForeground, Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "white"}, }, { Case: "Inherit background", Input: "test", Expected: "\x1b[41m\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "No parent", Input: "test", Expected: "\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Inherit override foreground", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[33mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override background", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override background, no foreground specified", Input: "hello <,parentBackground>world", Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit no parent foreground", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[37;49m\x1b[7mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Inherit no parent background", Input: "hello <,parentBackground>world", Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[30mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Inherit override both", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[41m\x1b[33mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "Inherit override both inverted", Input: "hello world", Expected: "\x1b[47m\x1b[30mhello \x1b[43m\x1b[31mworld\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, - Parent: &cachedColor{Foreground: "yellow", Background: "red"}, + Colors: &Colors{Foreground: "black", Background: "white"}, + Parent: &Colors{Foreground: "yellow", Background: "red"}, }, { Case: "Inline override", Input: "hello, world, rabbit", Expected: "\x1b[47m\x1b[30mhello, \x1b[31mworld\x1b[30m, rabbit\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Transparent background", Input: "hello world", Expected: "\x1b[37mhello world\x1b[0m", - Colors: &cachedColor{Foreground: "white", Background: Transparent}, + Colors: &Colors{Foreground: "white", Background: Transparent}, }, { Case: "Transparent foreground override", Input: "hello <#ffffff>world", Expected: "\x1b[32mhello \x1b[38;2;255;255;255mworld\x1b[0m", - Colors: &cachedColor{Foreground: "green", Background: Transparent}, + Colors: &Colors{Foreground: "green", Background: Transparent}, }, { Case: "No foreground", Input: "test", Expected: "\x1b[48;2;255;87;51m\x1b[37mtest\x1b[0m", - Colors: &cachedColor{Foreground: "", Background: "#FF5733"}, + Colors: &Colors{Foreground: "", Background: "#FF5733"}, }, { Case: "Transparent foreground", Input: "test", Expected: "\x1b[0m\x1b[38;2;255;87;51;49m\x1b[7mtest\x1b[0m", - Colors: &cachedColor{Foreground: Transparent, Background: "#FF5733"}, + Colors: &Colors{Foreground: Transparent, Background: "#FF5733"}, }, { Case: "Transparent foreground, terminal background set", Input: "test", Expected: "\x1b[38;2;33;47;60m\x1b[48;2;255;87;51mtest\x1b[0m", - Colors: &cachedColor{Foreground: Transparent, Background: "#FF5733"}, + Colors: &Colors{Foreground: Transparent, Background: "#FF5733"}, TerminalBackground: "#212F3C", }, { Case: "Foreground for foreground override", Input: "test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Background for background override", Input: "<,background>test", Expected: "\x1b[47m\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Google", Input: "Google", Expected: "\x1b[47m\x1b[34mG\x1b[40m\x1b[30m\x1b[47m\x1b[31mo\x1b[40m\x1b[30m\x1b[47m\x1b[33mo\x1b[40m\x1b[30m\x1b[47m\x1b[34mg\x1b[40m\x1b[30m\x1b[47m\x1b[32ml\x1b[40m\x1b[30m\x1b[47m\x1b[31me\x1b[0m", //nolint: lll - Colors: &cachedColor{Foreground: "black", Background: "black"}, + Colors: &Colors{Foreground: "black", Background: "black"}, }, { Case: "Foreground for background override", Input: "test", Expected: "\x1b[47m\x1b[37mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Foreground for background vice versa override", Input: "test", Expected: "\x1b[40m\x1b[37mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, { Case: "Background for foreground override", Input: "<,foreground>test", Expected: "\x1b[40m\x1b[30mtest\x1b[0m", - Colors: &cachedColor{Foreground: "black", Background: "white"}, + Colors: &Colors{Foreground: "black", Background: "white"}, }, } for _, tc := range cases { renderer := &Writer{ - ParentColors: []*cachedColor{tc.Parent}, + ParentColors: []*Colors{tc.Parent}, Colors: tc.Colors, TerminalBackground: tc.TerminalBackground, AnsiColors: &DefaultColors{}, @@ -210,37 +210,37 @@ func TestWriteLength(t *testing.T) { Case string Expected int Input string - Colors *cachedColor + Colors *Colors }{ { Case: "Bold", Input: "test", Expected: 4, - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override", Input: "<#ffffff>test", Expected: 4, - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override and link", Input: "<#ffffff>test [url](https://example.com)", Expected: 8, - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, { Case: "Bold with color override and link and leading/trailing spaces", Input: " <#ffffff>test [url](https://example.com) ", Expected: 10, - Colors: &cachedColor{Foreground: "black", Background: ParentBackground}, + Colors: &Colors{Foreground: "black", Background: ParentBackground}, }, } for _, tc := range cases { renderer := &Writer{ - ParentColors: []*cachedColor{}, + ParentColors: []*Colors{}, Colors: tc.Colors, AnsiColors: &DefaultColors{}, } diff --git a/src/ansi/colors.go b/src/ansi/colors.go index 120b4bd7..6a30b966 100644 --- a/src/ansi/colors.go +++ b/src/ansi/colors.go @@ -10,13 +10,13 @@ import ( "github.com/gookit/color" ) -// Colors is the interface that wraps AnsiColorFromString method. +// ColorString is the interface that wraps ToColor method. // -// AnsiColorFromString gets the ANSI color code for a given color string. +// ToColor gets the ANSI color code for a given color string. // This can include a valid hex color in the format `#FFFFFF`, // but also a name of one of the first 16 ANSI colors like `lightBlue`. -type Colors interface { - AnsiColorFromString(colorString string, isBackground bool) Color +type ColorString interface { + ToColor(colorString string, isBackground bool) Color } // Color is an ANSI color code ready to be printed to the console. @@ -48,7 +48,7 @@ func (c Color) ToForeground() Color { return c } -func MakeColors(palette Palette, cacheEnabled bool, accentColor string, env platform.Environment) (colors Colors) { +func MakeColors(palette Palette, cacheEnabled bool, accentColor string, env platform.Environment) (colors ColorString) { defaultColors := &DefaultColors{} defaultColors.SetAccentColor(env, accentColor) colors = defaultColors @@ -67,7 +67,7 @@ type RGB struct { // DefaultColors is the default AnsiColors implementation. type DefaultColors struct { - accent *cachedColor + accent *Colors } var ( @@ -98,7 +98,7 @@ const ( backgroundIndex = 1 ) -func (d *DefaultColors) AnsiColorFromString(colorString string, isBackground bool) Color { +func (d *DefaultColors) ToColor(colorString string, isBackground bool) Color { if len(colorString) == 0 { return emptyColor } @@ -157,24 +157,24 @@ func IsAnsiColorName(colorString string) bool { // PaletteColors is the AnsiColors Decorator that uses the Palette to do named color // lookups before ANSI color code generation. type PaletteColors struct { - ansiColors Colors + ansiColors ColorString palette Palette } -func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground bool) Color { +func (p *PaletteColors) ToColor(colorString string, isBackground bool) Color { paletteColor, err := p.palette.ResolveColor(colorString) if err != nil { return emptyColor } - ansiColor := p.ansiColors.AnsiColorFromString(paletteColor, isBackground) + ansiColor := p.ansiColors.ToColor(paletteColor, isBackground) return ansiColor } // CachedColors is the AnsiColors Decorator that does simple color lookup caching. -// AnsiColorFromString calls are cheap, but not free, and having a simple cache in +// ToColor calls are cheap, but not free, and having a simple cache in // has measurable positive effect on performance. type CachedColors struct { - ansiColors Colors + ansiColors ColorString colorCache map[cachedColorKey]Color } @@ -183,7 +183,7 @@ type cachedColorKey struct { isBackground bool } -func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) Color { +func (c *CachedColors) ToColor(colorString string, isBackground bool) Color { if c.colorCache == nil { c.colorCache = make(map[cachedColorKey]Color) } @@ -191,7 +191,7 @@ func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool if ansiColor, hit := c.colorCache[key]; hit { return ansiColor } - ansiColor := c.ansiColors.AnsiColorFromString(colorString, isBackground) + ansiColor := c.ansiColors.ToColor(colorString, isBackground) c.colorCache[key] = ansiColor return ansiColor } diff --git a/src/ansi/colors_test.go b/src/ansi/colors_test.go index dae79950..0b0847a4 100644 --- a/src/ansi/colors_test.go +++ b/src/ansi/colors_test.go @@ -26,7 +26,7 @@ func TestGetAnsiFromColorString(t *testing.T) { } for _, tc := range cases { ansiColors := &DefaultColors{} - ansiColor := ansiColors.AnsiColorFromString(tc.Color, tc.Background) + ansiColor := ansiColors.ToColor(tc.Color, tc.Background) assert.Equal(t, tc.Expected, ansiColor, tc.Case) } } diff --git a/src/ansi/colors_unix.go b/src/ansi/colors_unix.go index 49b85d95..7adcef4c 100644 --- a/src/ansi/colors_unix.go +++ b/src/ansi/colors_unix.go @@ -12,8 +12,8 @@ func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor st if len(defaultColor) == 0 { return } - d.accent = &cachedColor{ - Foreground: string(d.AnsiColorFromString(defaultColor, false)), - Background: string(d.AnsiColorFromString(defaultColor, true)), + d.accent = &Colors{ + Foreground: string(d.ToColor(defaultColor, false)), + Background: string(d.ToColor(defaultColor, true)), } } diff --git a/src/ansi/colors_windows.go b/src/ansi/colors_windows.go index 9ac956c9..79f61da3 100644 --- a/src/ansi/colors_windows.go +++ b/src/ansi/colors_windows.go @@ -27,15 +27,15 @@ func GetAccentColor(env platform.Environment) (*RGB, error) { func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) { rgb, err := GetAccentColor(env) if err != nil { - d.accent = &cachedColor{ - Foreground: string(d.AnsiColorFromString(defaultColor, false)), - Background: string(d.AnsiColorFromString(defaultColor, true)), + d.accent = &Colors{ + Foreground: string(d.ToColor(defaultColor, false)), + Background: string(d.ToColor(defaultColor, true)), } return } foreground := color.RGB(rgb.R, rgb.G, rgb.B, false) background := color.RGB(rgb.R, rgb.G, rgb.B, true) - d.accent = &cachedColor{ + d.accent = &Colors{ Foreground: foreground.String(), Background: background.String(), } diff --git a/src/ansi/cycle.go b/src/ansi/cycle.go new file mode 100644 index 00000000..f8de1641 --- /dev/null +++ b/src/ansi/cycle.go @@ -0,0 +1,10 @@ +package ansi + +type Cycle []*Colors + +func PopColors(array []*Colors) (*Colors, Cycle) { + if len(array) == 0 { + return nil, array + } + return array[0], array[1:] +} diff --git a/src/engine/block.go b/src/engine/block.go index 4d4323f6..55024cd8 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -55,11 +55,13 @@ type Block struct { writer *ansi.Writer activeSegment *Segment previousActiveSegment *Segment + cycle *ansi.Cycle } -func (b *Block) Init(env platform.Environment, writer *ansi.Writer) { +func (b *Block) Init(env platform.Environment, writer *ansi.Writer, cycle *ansi.Cycle) { b.env = env b.writer = writer + b.cycle = cycle b.executeSegmentLogic() } @@ -130,6 +132,11 @@ func (b *Block) RenderSegments() (string, int) { if !segment.Enabled && segment.style() != Accordion { continue } + if b.cycle != nil && len(*b.cycle) > 0 { + colors, cycle := ansi.PopColors(*b.cycle) + b.cycle = &cycle + segment.colors = colors + } b.setActiveSegment(segment) b.renderActiveSegment() } diff --git a/src/engine/config.go b/src/engine/config.go index 58b4b068..2ea4c1ab 100644 --- a/src/engine/config.go +++ b/src/engine/config.go @@ -47,6 +47,7 @@ type Config struct { DebugPrompt *Segment `json:"debug_prompt,omitempty"` Palette ansi.Palette `json:"palette,omitempty"` Palettes *ansi.Palettes `json:"palettes,omitempty"` + Cycle ansi.Cycle `json:"cycle,omitempty"` PWD string `json:"pwd,omitempty"` // Deprecated @@ -63,7 +64,7 @@ type Config struct { // MakeColors creates instance of AnsiColors to use in AnsiWriter according to // environment and configuration. -func (cfg *Config) MakeColors() ansi.Colors { +func (cfg *Config) MakeColors() ansi.ColorString { cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" return ansi.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) } diff --git a/src/engine/engine.go b/src/engine/engine.go index af143b13..d888cfc4 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -21,6 +21,7 @@ type Engine struct { currentLineLength int rprompt string rpromptLength int + cycle *ansi.Cycle } func (e *Engine) write(text string) { @@ -57,6 +58,8 @@ func (e *Engine) canWriteRightBlock(rprompt bool) bool { } func (e *Engine) PrintPrimary() string { + // cache a pointer to the color cycle + e.cycle = &e.Config.Cycle for _, block := range e.Config.Blocks { e.renderBlock(block) } @@ -156,7 +159,7 @@ func (e *Engine) renderBlock(block *Block) { if e.Env.Shell() == shell.BASH && (block.Type == RPrompt || block.Alignment == Right) { block.InitPlain(e.Env, e.Config) } else { - block.Init(e.Env, e.Writer) + block.Init(e.Env, e.Writer, e.cycle) } if !block.Enabled() { return @@ -243,9 +246,11 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string { duration: time.Since(titleStartTime), } segmentTimings = append(segmentTimings, segmentTiming) + // cache a pointer to the color cycle + e.cycle = &e.Config.Cycle // loop each segments of each blocks for _, block := range e.Config.Blocks { - block.Init(e.Env, e.Writer) + block.Init(e.Env, e.Writer, e.cycle) longestSegmentName, timings := block.Debug() segmentTimings = append(segmentTimings, timings...) if longestSegmentName > largestSegmentNameLength { @@ -344,7 +349,7 @@ func (e *Engine) PrintTooltip(tip string) string { } switch e.Env.Shell() { case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC: - block.Init(e.Env, e.Writer) + block.Init(e.Env, e.Writer, nil) if !block.Enabled() { return "" } @@ -453,7 +458,7 @@ func (e *Engine) PrintRPrompt() string { if block == nil { return "" } - block.Init(e.Env, e.Writer) + block.Init(e.Env, e.Writer, e.cycle) if !block.Enabled() { return "" } diff --git a/src/engine/segment.go b/src/engine/segment.go index 629ec861..cb3ccac7 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/jandedobbeleer/oh-my-posh/src/ansi" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/segments" @@ -39,13 +40,13 @@ type Segment struct { MaxWidth int `json:"max_width,omitempty"` MinWidth int `json:"min_width,omitempty"` - env platform.Environment - writer SegmentWriter - Enabled bool `json:"-"` - text string - backgroundCache string - foregroundCache string - styleCache SegmentStyle + Enabled bool `json:"-"` + + colors *ansi.Colors + env platform.Environment + writer SegmentWriter + text string + styleCache SegmentStyle } // SegmentTiming holds the timing context for a segment @@ -360,17 +361,23 @@ func (segment *Segment) shouldInvokeWithTip(tip string) bool { } func (segment *Segment) foreground() string { - if len(segment.foregroundCache) == 0 { - segment.foregroundCache = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground) + if segment.colors == nil { + segment.colors = &ansi.Colors{} } - return segment.foregroundCache + if len(segment.colors.Foreground) == 0 { + segment.colors.Foreground = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground) + } + return segment.colors.Foreground } func (segment *Segment) background() string { - if len(segment.backgroundCache) == 0 { - segment.backgroundCache = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background) + if segment.colors == nil { + segment.colors = &ansi.Colors{} } - return segment.backgroundCache + if len(segment.colors.Background) == 0 { + segment.colors.Background = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background) + } + return segment.colors.Background } func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error { diff --git a/themes/schema.json b/themes/schema.json index 45e18399..2558d3ac 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -3063,6 +3063,22 @@ } } }, + "cycle": { + "type": "array", + "title": "List of settings to cycle through segment by segment", + "description": "https://ohmyposh.dev/docs/configuration/cycle", + "default": [], + "items": { + "properties": { + "foreground": { + "$ref": "#/definitions/color" + }, + "background": { + "$ref": "#/definitions/color" + } + } + } + }, "accent_color": { "title": "Accent color", "$ref": "#/definitions/color" diff --git a/website/docs/configuration/colors.mdx b/website/docs/configuration/colors.mdx index db216a5d..1d794bed 100644 --- a/website/docs/configuration/colors.mdx +++ b/website/docs/configuration/colors.mdx @@ -236,6 +236,32 @@ you could also add `frappe` as the default `palette`, given that one is used as the `template` resolves to. In case no match is available and no `palette` is defined, it will also fallback to `transparent` for any palette color reference in templates/colors. +## Cycle + +When you want to display the same **sequence of colors** (background and foreground) regardless of which segments are active, you can +make use of the cycle property. This property is a list of colors which are used one after the other. If there's nothing in the list +anymore and your prompt would still render additional segments, the segment's colors are being used. A defined cycle always gets +precendence over everything else. + +```json +... +"cycle": [ + { + "background": "p:blue", + "foreground": "p:white" + }, + { + "background": "p:green", + "foreground": "p:black" + }, + { + "background": "p:orange", + "foreground": "p:white" + } +], +... +``` + [hexcolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ [ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ [git]: /docs/segments/git