Repository: JanDeDobbeleer/oh-my-posh
Pull request #1001 (2021-09-25 18:49:43)

fix: inherit parent colors in override
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [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

[Description of the change]

<!---

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.

-->

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/src/ansi_color.go b/src/ansi_color.go
index 772cbaa2..789aa7ae 100644
--- a/src/ansi_color.go
+++ b/src/ansi_color.go
@@ -47,6 +47,7 @@ type colorWriter interface {
 	write(background, foreground, text string)
 	string() string
 	reset()
+	setParentColors(background, foreground string)
 }
 
 // AnsiColor writes colorized strings
@@ -54,6 +55,12 @@ type AnsiColor struct {
 	builder            strings.Builder
 	ansi               *ansiUtils
 	terminalBackground string
+	Parent             *Color
+}
+
+type Color struct {
+	Background string
+	Foreground string
 }
 
 const (
@@ -63,6 +70,13 @@ const (
 	Inherit = "inherit"
 )
 
+func (a *AnsiColor) setParentColors(background, foreground string) {
+	a.Parent = &Color{
+		Background: background,
+		Foreground: foreground,
+	}
+}
+
 // 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`.
@@ -120,6 +134,12 @@ func (a *AnsiColor) write(background, foreground, text string) {
 	}
 
 	getAnsiColors := func(background, foreground string) (string, string) {
+		if background == Inherit && a.Parent != nil {
+			background = a.Parent.Background
+		}
+		if foreground == Inherit && a.Parent != nil {
+			foreground = a.Parent.Foreground
+		}
 		inverted := foreground == Transparent && len(background) != 0
 		background = a.getAnsiFromColorString(background, !inverted)
 		foreground = a.getAnsiFromColorString(foreground, false)
diff --git a/src/block.go b/src/block.go
index 1ad47621..8467b101 100644
--- a/src/block.go
+++ b/src/block.go
@@ -81,22 +81,6 @@ func (b *Block) setStringValues() {
 	}
 }
 
-func (b *Block) foreground() string {
-	color := b.activeSegment.foreground()
-	if b.previousActiveSegment != nil && color == Inherit {
-		return b.previousActiveSegment.foreground()
-	}
-	return color
-}
-
-func (b *Block) background() string {
-	color := b.activeSegment.background()
-	if b.previousActiveSegment != nil && color == Inherit {
-		return b.previousActiveSegment.background()
-	}
-	return color
-}
-
 func (b *Block) renderSegments() string {
 	defer b.writer.reset()
 	for _, segment := range b.Segments {
@@ -143,7 +127,7 @@ func (b *Block) getPowerlineColor(foreground bool) string {
 		return b.previousActiveSegment.background()
 	}
 	if b.activeSegment.Style == Diamond && len(b.activeSegment.LeadingDiamond) == 0 {
-		return b.background()
+		return b.activeSegment.background()
 	}
 	if !foreground && b.activeSegment.Style != Powerline {
 		return Transparent
@@ -164,10 +148,11 @@ func (b *Block) renderSegmentText(text string) {
 		b.renderPowerLineSegment(text)
 	}
 	b.previousActiveSegment = b.activeSegment
+	b.writer.setParentColors(b.activeSegment.background(), b.activeSegment.foreground())
 }
 
 func (b *Block) renderPowerLineSegment(text string) {
-	b.writePowerLineSeparator(b.background(), b.getPowerlineColor(true), false)
+	b.writePowerLineSeparator(b.activeSegment.background(), b.getPowerlineColor(true), false)
 	b.renderText(text)
 }
 
@@ -176,14 +161,14 @@ func (b *Block) renderPlainSegment(text string) {
 }
 
 func (b *Block) renderDiamondSegment(text string) {
-	b.writer.write(Transparent, b.background(), b.activeSegment.LeadingDiamond)
+	b.writer.write(Transparent, b.activeSegment.background(), b.activeSegment.LeadingDiamond)
 	b.renderText(text)
-	b.writer.write(Transparent, b.background(), b.activeSegment.TrailingDiamond)
+	b.writer.write(Transparent, b.activeSegment.background(), b.activeSegment.TrailingDiamond)
 }
 
 func (b *Block) renderText(text string) {
-	bg := b.background()
-	fg := b.foreground()
+	bg := b.activeSegment.background()
+	fg := b.activeSegment.foreground()
 	defaultValue := " "
 	b.writer.write(bg, fg, b.activeSegment.getValue(Prefix, defaultValue))
 	b.writer.write(bg, fg, text)


Test patch:
diff --git a/src/ansi_color_test.go b/src/ansi_color_test.go
index e9c4f50f..665024b5 100644
--- a/src/ansi_color_test.go
+++ b/src/ansi_color_test.go
@@ -3,165 +3,150 @@ package main
 import (
 	"testing"
 
-	"github.com/gookit/color"
 	"github.com/stretchr/testify/assert"
 )
 
-const (
-	inputText = "This is white, <#ff5733>this is orange</>, white again"
-)
-
-func TestWriteAndRemoveText(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := renderer.writeAndRemoveText("#193549", "#fff", "This is white, ", "This is white, ", inputText)
-	assert.Equal(t, "<#ff5733>this is orange</>, white again", text)
-	assert.NotContains(t, renderer.string(), "<#ff5733>")
-}
-
-func TestWriteAndRemoveTextColored(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := renderer.writeAndRemoveText("#193549", "#ff5733", "this is orange", "<#ff5733>this is orange</>", inputText)
-	assert.Equal(t, "This is white, , white again", text)
-	assert.NotContains(t, renderer.string(), "<#ff5733>")
-}
-
-func TestWriteColorOverride(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	renderer.write("#193549", "#ff5733", inputText)
-	assert.NotContains(t, renderer.string(), "<#ff5733>")
-}
-
-func TestWriteColorOverrideBackground(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := "This is white, <,#000000>this is black</>, white again"
-	renderer.write("#193549", "#ff5733", text)
-	assert.NotContains(t, renderer.string(), "000000")
-}
-
-func TestWriteColorOverrideBackground16(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := "This is default <,white> this background is changed</> default again"
-	renderer.write("#193549", "#ff5733", text)
-	assert.NotContains(t, renderer.string(), "white")
-	assert.NotContains(t, renderer.string(), "</>")
-	assert.NotContains(t, renderer.string(), "<,")
-}
-
-func TestWriteColorOverrideBoth(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := "This is white, <#000000,#ffffff>this is black</>, white again"
-	renderer.write("#193549", "#ff5733", text)
-	assert.NotContains(t, renderer.string(), "ffffff")
-	assert.NotContains(t, renderer.string(), "000000")
-}
-
-func TestWriteColorOverrideBoth16(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
+func TestGetAnsiFromColorString(t *testing.T) {
+	cases := []struct {
+		Case       string
+		Expected   string
+		Color      string
+		Background bool
+	}{
+		{Case: "Invalid background", Expected: "", Color: "invalid", Background: true},
+		{Case: "Invalid background", Expected: "", Color: "invalid", Background: false},
+		{Case: "Hex foreground", Expected: "48;2;170;187;204", Color: "#AABBCC", Background: false},
+		{Case: "Base 8 foreground", Expected: "41", Color: "red", Background: false},
+		{Case: "Base 8 background", Expected: "41", Color: "red", Background: true},
+		{Case: "Base 16 foreground", Expected: "101", Color: "lightRed", Background: false},
+		{Case: "Base 16 backround", Expected: "101", Color: "lightRed", Background: true},
 	}
-	text := "This is white, <black,white>this is black</>, white again"
-	renderer.write("#193549", "#ff5733", text)
-	assert.NotContains(t, renderer.string(), "<black,white>")
-	assert.NotContains(t, renderer.string(), "</>")
-}
-
-func TestWriteColorOverrideDouble(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
+	for _, tc := range cases {
+		renderer := &AnsiColor{}
+		ansiColor := renderer.getAnsiFromColorString(tc.Color, true)
+		assert.Equal(t, tc.Expected, ansiColor, tc.Case)
 	}
-	text := "<#ffffff>jan</>@<#ffffff>Jans-MBP</>"
-	renderer.write("#193549", "#ff5733", text)
-	assert.NotContains(t, renderer.string(), "<#ffffff>")
-	assert.NotContains(t, renderer.string(), "</>")
 }
 
-func TestWriteColorTransparent(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
+func TestWriteANSIColors(t *testing.T) {
+	cases := []struct {
+		Case               string
+		Expected           string
+		Input              string
+		Colors             *Color
+		Parent             *Color
+		TerminalBackground string
+	}{
+		{
+			Case:     "No color override",
+			Input:    "test",
+			Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+			Parent:   &Color{Foreground: "black", Background: "white"},
+		},
+		{
+			Case:     "Inherit foreground",
+			Input:    "test",
+			Expected: "\x1b[47m\x1b[33mtest\x1b[0m",
+			Colors:   &Color{Foreground: Inherit, Background: "white"},
+			Parent:   &Color{Foreground: "yellow", Background: "white"},
+		},
+		{
+			Case:     "Inherit background",
+			Input:    "test",
+			Expected: "\x1b[41m\x1b[30mtest\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: Inherit},
+			Parent:   &Color{Foreground: "yellow", Background: "red"},
+		},
+		{
+			Case:     "No parent",
+			Input:    "test",
+			Expected: "\x1b[30mtest\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: Inherit},
+		},
+		{
+			Case:     "Inherit override foreground",
+			Input:    "hello <inherit>world</>",
+			Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[47m\x1b[33mworld\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+			Parent:   &Color{Foreground: "yellow", Background: "red"},
+		},
+		{
+			Case:     "Inherit override background",
+			Input:    "hello <black,inherit>world</>",
+			Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[41m\x1b[30mworld\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+			Parent:   &Color{Foreground: "yellow", Background: "red"},
+		},
+		{
+			Case:     "Inherit override background, no foreground specified",
+			Input:    "hello <,inherit>world</>",
+			Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[41m\x1b[30mworld\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+			Parent:   &Color{Foreground: "yellow", Background: "red"},
+		},
+		{
+			Case:     "Inherit override both",
+			Input:    "hello <inherit,inherit>world</>",
+			Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[41m\x1b[33mworld\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+			Parent:   &Color{Foreground: "yellow", Background: "red"},
+		},
+		{
+			Case:     "Inline override",
+			Input:    "hello, <red>world</>, rabbit",
+			Expected: "\x1b[47m\x1b[30mhello, \x1b[0m\x1b[47m\x1b[31mworld\x1b[0m\x1b[47m\x1b[30m, rabbit\x1b[0m",
+			Colors:   &Color{Foreground: "black", Background: "white"},
+		},
+		{
+			Case:     "Transparent background",
+			Input:    "hello world",
+			Expected: "\x1b[37mhello world\x1b[0m",
+			Colors:   &Color{Foreground: "white", Background: Transparent},
+		},
+		{
+			Case:     "Transparent foreground override",
+			Input:    "hello <#ffffff>world</>",
+			Expected: "\x1b[32mhello \x1b[0m\x1b[38;2;255;255;255mworld\x1b[0m",
+			Colors:   &Color{Foreground: "green", Background: Transparent},
+		},
+		{
+			Case:     "Double override",
+			Input:    "<#ffffff>jan</>@<#ffffff>Jans-MBP</>",
+			Expected: "\x1b[48;2;255;87;51m\x1b[38;2;255;255;255mjan\x1b[0m\x1b[48;2;255;87;51m\x1b[32m@\x1b[0m\x1b[48;2;255;87;51m\x1b[38;2;255;255;255mJans-MBP\x1b[0m",
+			Colors:   &Color{Foreground: "green", Background: "#FF5733"},
+		},
+		{
+			Case:     "No foreground",
+			Input:    "test",
+			Expected: "\x1b[48;2;255;87;51m\x1b[37mtest\x1b[0m",
+			Colors:   &Color{Foreground: "", Background: "#FF5733"},
+		},
+		{
+			Case:     "Transparent foreground",
+			Input:    "test",
+			Expected: "\x1b[38;2;255;87;51;49m\x1b[7mtest\x1b[0m",
+			Colors:   &Color{Foreground: Transparent, Background: "#FF5733"},
+		},
+		{
+			Case:               "Transparent foreground, terminal background set",
+			Input:              "test",
+			Expected:           "\x1b[38;2;255;87;51m\x1b[38;2;33;47;60mtest\x1b[0m",
+			Colors:             &Color{Foreground: Transparent, Background: "#FF5733"},
+			TerminalBackground: "#212F3C",
+		},
 	}
-	text := "This is white"
-	renderer.writeColoredText("#193549", Transparent, text)
-	t.Log(renderer.string())
-}
 
-func TestWriteColorName(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
+	for _, tc := range cases {
+		ansi := &ansiUtils{}
+		ansi.init("pwsh")
+		renderer := &AnsiColor{
+			ansi:               ansi,
+			Parent:             tc.Parent,
+			terminalBackground: tc.TerminalBackground,
+		}
+		renderer.write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
+		got := renderer.string()
+		assert.Equal(t, tc.Expected, got, tc.Case)
 	}
-	text := "This is white, <red>this is red</>, white again"
-	renderer.write("#193549", "red", text)
-	assert.NotContains(t, renderer.string(), "<red>")
-}
-
-func TestWriteColorInvalid(t *testing.T) {
-	ansi := &ansiUtils{}
-	ansi.init("pwsh")
-	renderer := &AnsiColor{
-		ansi: ansi,
-	}
-	text := "This is white, <invalid>this is orange</>, white again"
-	renderer.write("#193549", "invalid", text)
-	assert.NotContains(t, renderer.string(), "<invalid>")
-}
-
-func TestGetAnsiFromColorStringBg(t *testing.T) {
-	renderer := &AnsiColor{}
-	colorCode := renderer.getAnsiFromColorString("blue", true)
-	assert.Equal(t, color.BgBlue.String(), colorCode)
-}
-
-func TestGetAnsiFromColorStringFg(t *testing.T) {
-	renderer := &AnsiColor{}
-	colorCode := renderer.getAnsiFromColorString("red", false)
-	assert.Equal(t, color.FgRed.String(), colorCode)
-}
-
-func TestGetAnsiFromColorStringHex(t *testing.T) {
-	renderer := &AnsiColor{}
-	colorCode := renderer.getAnsiFromColorString("#AABBCC", false)
-	assert.Equal(t, color.HEX("#AABBCC").String(), colorCode)
-}
-
-func TestGetAnsiFromColorStringInvalidFg(t *testing.T) {
-	renderer := &AnsiColor{}
-	colorCode := renderer.getAnsiFromColorString("invalid", false)
-	assert.Equal(t, "", colorCode)
-}
-
-func TestGetAnsiFromColorStringInvalidBg(t *testing.T) {
-	renderer := &AnsiColor{}
-	colorCode := renderer.getAnsiFromColorString("invalid", true)
-	assert.Equal(t, "", colorCode)
 }
diff --git a/src/block_test.go b/src/block_test.go
index 73b922dc..f3ba9380 100644
--- a/src/block_test.go
+++ b/src/block_test.go
@@ -28,42 +28,42 @@ func TestBlockEnabled(t *testing.T) {
 	}
 }
 
-func TestForegroundColor(t *testing.T) {
-	cases := []struct {
-		Case            string
-		Expected        string
-		PreviousSegment *Segment
-		Segment         *Segment
-	}{
-		{Case: "Standard", Expected: "black", Segment: &Segment{Foreground: "black"}},
-		{Case: "Segment color override", Expected: "red", Segment: &Segment{Foreground: "black", props: &properties{foreground: "red"}}},
-		{Case: "Inherit", Expected: "yellow", Segment: &Segment{Foreground: Inherit}, PreviousSegment: &Segment{Foreground: "yellow"}},
-	}
+// func TestForegroundColor(t *testing.T) {
+// 	cases := []struct {
+// 		Case            string
+// 		Expected        string
+// 		PreviousSegment *Segment
+// 		Segment         *Segment
+// 	}{
+// 		{Case: "Standard", Expected: "black", Segment: &Segment{Foreground: "black"}},
+// 		{Case: "Segment color override", Expected: "red", Segment: &Segment{Foreground: "black", props: &properties{foreground: "red"}}},
+// 		{Case: "Inherit", Expected: "yellow", Segment: &Segment{Foreground: Inherit}, PreviousSegment: &Segment{Foreground: "yellow"}},
+// 	}
 
-	for _, tc := range cases {
-		block := &Block{}
-		block.previousActiveSegment = tc.PreviousSegment
-		block.activeSegment = tc.Segment
-		assert.Equal(t, tc.Expected, block.foreground(), tc.Case)
-	}
-}
+// 	for _, tc := range cases {
+// 		block := &Block{}
+// 		block.previousActiveSegment = tc.PreviousSegment
+// 		block.activeSegment = tc.Segment
+// 		assert.Equal(t, tc.Expected, block.foreground(), tc.Case)
+// 	}
+// }
 
-func TestBackgroundColor(t *testing.T) {
-	cases := []struct {
-		Case            string
-		Expected        string
-		PreviousSegment *Segment
-		Segment         *Segment
-	}{
-		{Case: "Standard", Expected: "black", Segment: &Segment{Background: "black"}},
-		{Case: "Segment color override", Expected: "red", Segment: &Segment{Background: "black", props: &properties{background: "red"}}},
-		{Case: "Inherit", Expected: "yellow", Segment: &Segment{Background: Inherit}, PreviousSegment: &Segment{Background: "yellow"}},
-	}
+// func TestBackgroundColor(t *testing.T) {
+// 	cases := []struct {
+// 		Case            string
+// 		Expected        string
+// 		PreviousSegment *Segment
+// 		Segment         *Segment
+// 	}{
+// 		{Case: "Standard", Expected: "black", Segment: &Segment{Background: "black"}},
+// 		{Case: "Segment color override", Expected: "red", Segment: &Segment{Background: "black", props: &properties{background: "red"}}},
+// 		{Case: "Inherit", Expected: "yellow", Segment: &Segment{Background: Inherit}, PreviousSegment: &Segment{Background: "yellow"}},
+// 	}
 
-	for _, tc := range cases {
-		block := &Block{}
-		block.previousActiveSegment = tc.PreviousSegment
-		block.activeSegment = tc.Segment
-		assert.Equal(t, tc.Expected, block.background(), tc.Case)
-	}
-}
+// 	for _, tc := range cases {
+// 		block := &Block{}
+// 		block.previousActiveSegment = tc.PreviousSegment
+// 		block.activeSegment = tc.Segment
+// 		assert.Equal(t, tc.Expected, block.background(), tc.Case)
+// 	}
+// }