Repository: JanDeDobbeleer/oh-my-posh
Pull request #3341 (2023-01-10 11:38:53)

tuesday blues
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md]
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added/updated (for bug fixes/features)

### Description

- fix(zsh): tooltip compatibility with oh-my-zsh
- feat: cycle through colors
- fix: adjust Azure DevOps icon

<!---

Tips:

If you're not comfortable with working with Git, we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
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/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_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{}
+	}
+	if len(segment.colors.Background) == 0 {
+		segment.colors.Background = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background)
 	}
-	return segment.backgroundCache
+	return segment.colors.Background
 }
 
 func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error {
diff --git a/src/segments/git.go b/src/segments/git.go
index cc13b716..fe208e89 100644
--- a/src/segments/git.go
+++ b/src/segments/git.go
@@ -408,8 +408,8 @@ func (g *Git) getUpstreamIcon() string {
 		"github":           {GithubIcon, "\uF408 "},
 		"gitlab":           {GitlabIcon, "\uF296 "},
 		"bitbucket":        {BitbucketIcon, "\uF171 "},
-		"dev.azure.com":    {AzureDevOpsIcon, "\uFD03 "},
-		"visualstudio.com": {AzureDevOpsIcon, "\uFD03 "},
+		"dev.azure.com":    {AzureDevOpsIcon, "\uEBE8 "},
+		"visualstudio.com": {AzureDevOpsIcon, "\uEBE8 "},
 	}
 	for key, value := range defaults {
 		if strings.Contains(g.UpstreamURL, key) {
diff --git a/src/shell/scripts/omp.zsh b/src/shell/scripts/omp.zsh
index 97cf9d7a..16d45778 100644
--- a/src/shell/scripts/omp.zsh
+++ b/src/shell/scripts/omp.zsh
@@ -50,35 +50,30 @@ if [ "$TERM" != "linux" ]; then
 fi
 
 # perform cleanup so a new initialization in current session works
-if [[ "$(zle -lL self-insert)" = *"_posh-self-insert"* ]]; then
+if [[ "$(zle -lL self-insert)" = *"_posh-tooltip"* ]]; then
   zle -N self-insert
 fi
 if [[ "$(zle -lL zle-line-init)" = *"_posh-zle-line-init"* ]]; then
   zle -N zle-line-init
 fi
 
-function _posh-self-insert() {
+function _posh-tooltip() {
   # ignore an empty buffer
   if [[ -z  "$BUFFER"  ]]; then
     zle .self-insert
     return
   fi
-  # trigger a tip check only if the input is a space character
-  if [[ "$KEYS" = " " ]]; then
-    local tooltip=$(::OMP:: print tooltip --config="$POSH_THEME" --shell=zsh --error="$omp_last_error" --command="$BUFFER" --shell-version="$ZSH_VERSION")
-  fi
+
+  local tooltip=$(::OMP:: print tooltip --config="$POSH_THEME" --shell=zsh --error="$omp_last_error" --command="$BUFFER" --shell-version="$ZSH_VERSION")
   # ignore an empty tooltip
   if [[ -n "$tooltip" ]]; then
     RPROMPT=$tooltip
     zle .reset-prompt
   fi
+
   zle .self-insert
 }
 
-if [[ "::TOOLTIPS::" = "true" ]]; then
-  zle -N self-insert _posh-self-insert
-fi
-
 function _posh-zle-line-init() {
     [[ $CONTEXT == start ]] || return 0
 
@@ -106,7 +101,12 @@ function _posh-zle-line-init() {
     return ret
 }
 
-if [[ "::TRANSIENT::" = "true" ]]; then
+function enable_poshtooltips() {
+  zle -N _posh-tooltip
+  bindkey " " _posh-tooltip
+}
+
+function enable_poshtransientprompt() {
   zle -N zle-line-init _posh-zle-line-init
 
   # restore broken key bindings
@@ -120,8 +120,12 @@ if [[ "::TRANSIENT::" = "true" ]]; then
   if [[ -n "${_widgets[(r)up-line-or-beginning-search]}" ]]; then
     bindkey '^[[A' up-line-or-beginning-search
   fi
+}
+
+if [[ "::TOOLTIPS::" = "true" ]]; then
+  enable_poshtooltips
 fi
 
-# legacy functions for backwards compatibility
-function enable_poshtooltips() {}
-function enable_poshtransientprompt() {}
+if [[ "::TRANSIENT::" = "true" ]]; then
+  enable_poshtransientprompt
+fi
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
diff --git a/website/docs/segments/git.mdx b/website/docs/segments/git.mdx
index eaead6c1..defc07b5 100644
--- a/website/docs/segments/git.mdx
+++ b/website/docs/segments/git.mdx
@@ -109,7 +109,7 @@ You can set the following properties to `true` to enable fetching additional inf
 | `github_icon`       | `string`            | icon/text to display when the upstream is Github - defaults to `\uF408 `                                                                                                     |
 | `gitlab_icon`       | `string`            | icon/text to display when the upstream is Gitlab - defaults to `\uF296 `                                                                                                     |
 | `bitbucket_icon`    | `string`            | icon/text to display when the upstream is Bitbucket - defaults to `\uF171 `                                                                                                  |
-| `azure_devops_icon` | `string`            | icon/text to display when the upstream is Azure DevOps - defaults to `\uFD03 `                                                                                               |
+| `azure_devops_icon` | `string`            | icon/text to display when the upstream is Azure DevOps - defaults to `\uEBE8 `                                                                                               |
 | `git_icon`          | `string`            | icon/text to display when the upstream is not known/mapped - defaults to `\uE5FB `                                                                                           |
 | `upstream_icons`    | `map[string]string` | a key, value map representing the remote URL (or a part of that URL) and icon to use in case the upstream URL contains the key. These get precedence over the standard icons |
 


Test patch:
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:    "<b>test</b>",
 			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:    "<b><#ffffff>test</></b>",
 			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><b>test</b></>",
 			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 <parentForeground>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 <black,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 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 <parentForeground>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 <parentForeground,parentBackground>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 <parentBackground,parentForeground>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, <red>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:    "<foreground>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:    "<blue,white>G</><red,white>o</><yellow,white>o</><blue,white>g</><green,white>l</><red,white>e</>",
 			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:    "<background>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:    "<background,foreground>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:    "<b>test</b>",
 			Expected: 4,
-			Colors:   &cachedColor{Foreground: "black", Background: ParentBackground},
+			Colors:   &Colors{Foreground: "black", Background: ParentBackground},
 		},
 		{
 			Case:     "Bold with color override",
 			Input:    "<b><#ffffff>test</></b>",
 			Expected: 4,
-			Colors:   &cachedColor{Foreground: "black", Background: ParentBackground},
+			Colors:   &Colors{Foreground: "black", Background: ParentBackground},
 		},
 		{
 			Case:     "Bold with color override and link",
 			Input:    "<b><#ffffff>test</></b> [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:    " <b><#ffffff>test</></b> [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_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)
 	}
 }