mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-03-05 20:49:04 -08:00
parent
6ca3d22b80
commit
86459f9a2f
|
@ -34,9 +34,9 @@ type style struct {
|
||||||
End string
|
End string
|
||||||
}
|
}
|
||||||
|
|
||||||
type cachedColor struct {
|
type Colors struct {
|
||||||
Background string
|
Background string `json:"background"`
|
||||||
Foreground string
|
Foreground string `json:"foreground"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -75,9 +75,9 @@ const (
|
||||||
// Writer writes colorized ANSI strings
|
// Writer writes colorized ANSI strings
|
||||||
type Writer struct {
|
type Writer struct {
|
||||||
TerminalBackground string
|
TerminalBackground string
|
||||||
Colors *cachedColor
|
Colors *Colors
|
||||||
ParentColors []*cachedColor
|
ParentColors []*Colors
|
||||||
AnsiColors Colors
|
AnsiColors ColorString
|
||||||
Plain bool
|
Plain bool
|
||||||
|
|
||||||
builder strings.Builder
|
builder strings.Builder
|
||||||
|
@ -175,7 +175,7 @@ func (w *Writer) Init(shellName string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Writer) SetColors(background, foreground string) {
|
func (w *Writer) SetColors(background, foreground string) {
|
||||||
w.Colors = &cachedColor{
|
w.Colors = &Colors{
|
||||||
Background: background,
|
Background: background,
|
||||||
Foreground: foreground,
|
Foreground: foreground,
|
||||||
}
|
}
|
||||||
|
@ -183,9 +183,9 @@ func (w *Writer) SetColors(background, foreground string) {
|
||||||
|
|
||||||
func (w *Writer) SetParentColors(background, foreground string) {
|
func (w *Writer) SetParentColors(background, foreground string) {
|
||||||
if w.ParentColors == nil {
|
if w.ParentColors == nil {
|
||||||
w.ParentColors = make([]*cachedColor, 0)
|
w.ParentColors = make([]*Colors, 0)
|
||||||
}
|
}
|
||||||
w.ParentColors = append([]*cachedColor{{
|
w.ParentColors = append([]*Colors{{
|
||||||
Background: background,
|
Background: background,
|
||||||
Foreground: foreground,
|
Foreground: foreground,
|
||||||
}}, w.ParentColors...)
|
}}, w.ParentColors...)
|
||||||
|
@ -279,7 +279,7 @@ func (w *Writer) Write(background, foreground, text string) {
|
||||||
w.background, w.foreground = w.asAnsiColors(background, foreground)
|
w.background, w.foreground = w.asAnsiColors(background, foreground)
|
||||||
// default to white foreground
|
// default to white foreground
|
||||||
if w.foreground.IsEmpty() {
|
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
|
// validate if we start with a color override
|
||||||
match := regex.FindNamedRegexMatch(anchorRegex, text)
|
match := regex.FindNamedRegexMatch(anchorRegex, text)
|
||||||
|
@ -361,7 +361,7 @@ func (w *Writer) writeEscapedAnsiString(text string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color {
|
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() {
|
func (w *Writer) writeSegmentColors() {
|
||||||
|
|
|
@ -13,187 +13,187 @@ func TestWriteANSIColors(t *testing.T) {
|
||||||
Case string
|
Case string
|
||||||
Expected string
|
Expected string
|
||||||
Input string
|
Input string
|
||||||
Colors *cachedColor
|
Colors *Colors
|
||||||
Parent *cachedColor
|
Parent *Colors
|
||||||
TerminalBackground string
|
TerminalBackground string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Case: "Bold",
|
Case: "Bold",
|
||||||
Input: "<b>test</b>",
|
Input: "<b>test</b>",
|
||||||
Expected: "\x1b[1m\x1b[30mtest\x1b[22m\x1b[0m",
|
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",
|
Case: "Bold with color override",
|
||||||
Input: "<b><#ffffff>test</></b>",
|
Input: "<b><#ffffff>test</></b>",
|
||||||
Expected: "\x1b[1m\x1b[30m\x1b[38;2;255;255;255mtest\x1b[0m\x1b[30m\x1b[22m\x1b[0m",
|
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",
|
Case: "Bold with color override, flavor 2",
|
||||||
Input: "<#ffffff><b>test</b></>",
|
Input: "<#ffffff><b>test</b></>",
|
||||||
Expected: "\x1b[38;2;255;255;255m\x1b[1mtest\x1b[22m\x1b[0m",
|
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",
|
Case: "Double override",
|
||||||
Input: "<#ffffff>jan</>@<#ffffff>Jans-MBP</>",
|
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",
|
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",
|
Case: "No color override",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "black", Background: "white"},
|
Parent: &Colors{Foreground: "black", Background: "white"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit foreground",
|
Case: "Inherit foreground",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[47m\x1b[33mtest\x1b[0m",
|
Expected: "\x1b[47m\x1b[33mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: ParentForeground, Background: "white"},
|
Colors: &Colors{Foreground: ParentForeground, Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "white"},
|
Parent: &Colors{Foreground: "yellow", Background: "white"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit background",
|
Case: "Inherit background",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[41m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[41m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "No parent",
|
Case: "No parent",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit override foreground",
|
Case: "Inherit override foreground",
|
||||||
Input: "hello <parentForeground>world</>",
|
Input: "hello <parentForeground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[33mworld\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mhello \x1b[33mworld\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit override background",
|
Case: "Inherit override background",
|
||||||
Input: "hello <black,parentBackground>world</>",
|
Input: "hello <black,parentBackground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit override background, no foreground specified",
|
Case: "Inherit override background, no foreground specified",
|
||||||
Input: "hello <,parentBackground>world</>",
|
Input: "hello <,parentBackground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mhello \x1b[41mworld\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit no parent foreground",
|
Case: "Inherit no parent foreground",
|
||||||
Input: "hello <parentForeground>world</>",
|
Input: "hello <parentForeground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[37;49m\x1b[7mworld\x1b[0m",
|
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",
|
Case: "Inherit no parent background",
|
||||||
Input: "hello <,parentBackground>world</>",
|
Input: "hello <,parentBackground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[0m\x1b[30mworld\x1b[0m",
|
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",
|
Case: "Inherit override both",
|
||||||
Input: "hello <parentForeground,parentBackground>world</>",
|
Input: "hello <parentForeground,parentBackground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[41m\x1b[33mworld\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mhello \x1b[41m\x1b[33mworld\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inherit override both inverted",
|
Case: "Inherit override both inverted",
|
||||||
Input: "hello <parentBackground,parentForeground>world</>",
|
Input: "hello <parentBackground,parentForeground>world</>",
|
||||||
Expected: "\x1b[47m\x1b[30mhello \x1b[43m\x1b[31mworld\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mhello \x1b[43m\x1b[31mworld\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
Parent: &cachedColor{Foreground: "yellow", Background: "red"},
|
Parent: &Colors{Foreground: "yellow", Background: "red"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Inline override",
|
Case: "Inline override",
|
||||||
Input: "hello, <red>world</>, rabbit",
|
Input: "hello, <red>world</>, rabbit",
|
||||||
Expected: "\x1b[47m\x1b[30mhello, \x1b[31mworld\x1b[30m, rabbit\x1b[0m",
|
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",
|
Case: "Transparent background",
|
||||||
Input: "hello world",
|
Input: "hello world",
|
||||||
Expected: "\x1b[37mhello world\x1b[0m",
|
Expected: "\x1b[37mhello world\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "white", Background: Transparent},
|
Colors: &Colors{Foreground: "white", Background: Transparent},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Transparent foreground override",
|
Case: "Transparent foreground override",
|
||||||
Input: "hello <#ffffff>world</>",
|
Input: "hello <#ffffff>world</>",
|
||||||
Expected: "\x1b[32mhello \x1b[38;2;255;255;255mworld\x1b[0m",
|
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",
|
Case: "No foreground",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[48;2;255;87;51m\x1b[37mtest\x1b[0m",
|
Expected: "\x1b[48;2;255;87;51m\x1b[37mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "", Background: "#FF5733"},
|
Colors: &Colors{Foreground: "", Background: "#FF5733"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Transparent foreground",
|
Case: "Transparent foreground",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[0m\x1b[38;2;255;87;51;49m\x1b[7mtest\x1b[0m",
|
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",
|
Case: "Transparent foreground, terminal background set",
|
||||||
Input: "test",
|
Input: "test",
|
||||||
Expected: "\x1b[38;2;33;47;60m\x1b[48;2;255;87;51mtest\x1b[0m",
|
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",
|
TerminalBackground: "#212F3C",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Foreground for foreground override",
|
Case: "Foreground for foreground override",
|
||||||
Input: "<foreground>test</>",
|
Input: "<foreground>test</>",
|
||||||
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Background for background override",
|
Case: "Background for background override",
|
||||||
Input: "<,background>test</>",
|
Input: "<,background>test</>",
|
||||||
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[47m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Google",
|
Case: "Google",
|
||||||
Input: "<blue,white>G</><red,white>o</><yellow,white>o</><blue,white>g</><green,white>l</><red,white>e</>",
|
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
|
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",
|
Case: "Foreground for background override",
|
||||||
Input: "<background>test</>",
|
Input: "<background>test</>",
|
||||||
Expected: "\x1b[47m\x1b[37mtest\x1b[0m",
|
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",
|
Case: "Foreground for background vice versa override",
|
||||||
Input: "<background,foreground>test</>",
|
Input: "<background,foreground>test</>",
|
||||||
Expected: "\x1b[40m\x1b[37mtest\x1b[0m",
|
Expected: "\x1b[40m\x1b[37mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Background for foreground override",
|
Case: "Background for foreground override",
|
||||||
Input: "<,foreground>test</>",
|
Input: "<,foreground>test</>",
|
||||||
Expected: "\x1b[40m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[40m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &cachedColor{Foreground: "black", Background: "white"},
|
Colors: &Colors{Foreground: "black", Background: "white"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
renderer := &Writer{
|
renderer := &Writer{
|
||||||
ParentColors: []*cachedColor{tc.Parent},
|
ParentColors: []*Colors{tc.Parent},
|
||||||
Colors: tc.Colors,
|
Colors: tc.Colors,
|
||||||
TerminalBackground: tc.TerminalBackground,
|
TerminalBackground: tc.TerminalBackground,
|
||||||
AnsiColors: &DefaultColors{},
|
AnsiColors: &DefaultColors{},
|
||||||
|
@ -210,37 +210,37 @@ func TestWriteLength(t *testing.T) {
|
||||||
Case string
|
Case string
|
||||||
Expected int
|
Expected int
|
||||||
Input string
|
Input string
|
||||||
Colors *cachedColor
|
Colors *Colors
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Case: "Bold",
|
Case: "Bold",
|
||||||
Input: "<b>test</b>",
|
Input: "<b>test</b>",
|
||||||
Expected: 4,
|
Expected: 4,
|
||||||
Colors: &cachedColor{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Bold with color override",
|
Case: "Bold with color override",
|
||||||
Input: "<b><#ffffff>test</></b>",
|
Input: "<b><#ffffff>test</></b>",
|
||||||
Expected: 4,
|
Expected: 4,
|
||||||
Colors: &cachedColor{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Bold with color override and link",
|
Case: "Bold with color override and link",
|
||||||
Input: "<b><#ffffff>test</></b> [url](https://example.com)",
|
Input: "<b><#ffffff>test</></b> [url](https://example.com)",
|
||||||
Expected: 8,
|
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",
|
Case: "Bold with color override and link and leading/trailing spaces",
|
||||||
Input: " <b><#ffffff>test</></b> [url](https://example.com) ",
|
Input: " <b><#ffffff>test</></b> [url](https://example.com) ",
|
||||||
Expected: 10,
|
Expected: 10,
|
||||||
Colors: &cachedColor{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
renderer := &Writer{
|
renderer := &Writer{
|
||||||
ParentColors: []*cachedColor{},
|
ParentColors: []*Colors{},
|
||||||
Colors: tc.Colors,
|
Colors: tc.Colors,
|
||||||
AnsiColors: &DefaultColors{},
|
AnsiColors: &DefaultColors{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
"github.com/gookit/color"
|
"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`,
|
// 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`.
|
// but also a name of one of the first 16 ANSI colors like `lightBlue`.
|
||||||
type Colors interface {
|
type ColorString interface {
|
||||||
AnsiColorFromString(colorString string, isBackground bool) Color
|
ToColor(colorString string, isBackground bool) Color
|
||||||
}
|
}
|
||||||
|
|
||||||
// Color is an ANSI color code ready to be printed to the console.
|
// Color is an ANSI color code ready to be printed to the console.
|
||||||
|
@ -48,7 +48,7 @@ func (c Color) ToForeground() Color {
|
||||||
return c
|
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 := &DefaultColors{}
|
||||||
defaultColors.SetAccentColor(env, accentColor)
|
defaultColors.SetAccentColor(env, accentColor)
|
||||||
colors = defaultColors
|
colors = defaultColors
|
||||||
|
@ -67,7 +67,7 @@ type RGB struct {
|
||||||
|
|
||||||
// DefaultColors is the default AnsiColors implementation.
|
// DefaultColors is the default AnsiColors implementation.
|
||||||
type DefaultColors struct {
|
type DefaultColors struct {
|
||||||
accent *cachedColor
|
accent *Colors
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -98,7 +98,7 @@ const (
|
||||||
backgroundIndex = 1
|
backgroundIndex = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *DefaultColors) AnsiColorFromString(colorString string, isBackground bool) Color {
|
func (d *DefaultColors) ToColor(colorString string, isBackground bool) Color {
|
||||||
if len(colorString) == 0 {
|
if len(colorString) == 0 {
|
||||||
return emptyColor
|
return emptyColor
|
||||||
}
|
}
|
||||||
|
@ -157,24 +157,24 @@ func IsAnsiColorName(colorString string) bool {
|
||||||
// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color
|
// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color
|
||||||
// lookups before ANSI color code generation.
|
// lookups before ANSI color code generation.
|
||||||
type PaletteColors struct {
|
type PaletteColors struct {
|
||||||
ansiColors Colors
|
ansiColors ColorString
|
||||||
palette Palette
|
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)
|
paletteColor, err := p.palette.ResolveColor(colorString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return emptyColor
|
return emptyColor
|
||||||
}
|
}
|
||||||
ansiColor := p.ansiColors.AnsiColorFromString(paletteColor, isBackground)
|
ansiColor := p.ansiColors.ToColor(paletteColor, isBackground)
|
||||||
return ansiColor
|
return ansiColor
|
||||||
}
|
}
|
||||||
|
|
||||||
// CachedColors is the AnsiColors Decorator that does simple color lookup caching.
|
// 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.
|
// has measurable positive effect on performance.
|
||||||
type CachedColors struct {
|
type CachedColors struct {
|
||||||
ansiColors Colors
|
ansiColors ColorString
|
||||||
colorCache map[cachedColorKey]Color
|
colorCache map[cachedColorKey]Color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ type cachedColorKey struct {
|
||||||
isBackground bool
|
isBackground bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) Color {
|
func (c *CachedColors) ToColor(colorString string, isBackground bool) Color {
|
||||||
if c.colorCache == nil {
|
if c.colorCache == nil {
|
||||||
c.colorCache = make(map[cachedColorKey]Color)
|
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 {
|
if ansiColor, hit := c.colorCache[key]; hit {
|
||||||
return ansiColor
|
return ansiColor
|
||||||
}
|
}
|
||||||
ansiColor := c.ansiColors.AnsiColorFromString(colorString, isBackground)
|
ansiColor := c.ansiColors.ToColor(colorString, isBackground)
|
||||||
c.colorCache[key] = ansiColor
|
c.colorCache[key] = ansiColor
|
||||||
return ansiColor
|
return ansiColor
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestGetAnsiFromColorString(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
ansiColors := &DefaultColors{}
|
ansiColors := &DefaultColors{}
|
||||||
ansiColor := ansiColors.AnsiColorFromString(tc.Color, tc.Background)
|
ansiColor := ansiColors.ToColor(tc.Color, tc.Background)
|
||||||
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
|
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor st
|
||||||
if len(defaultColor) == 0 {
|
if len(defaultColor) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.accent = &cachedColor{
|
d.accent = &Colors{
|
||||||
Foreground: string(d.AnsiColorFromString(defaultColor, false)),
|
Foreground: string(d.ToColor(defaultColor, false)),
|
||||||
Background: string(d.AnsiColorFromString(defaultColor, true)),
|
Background: string(d.ToColor(defaultColor, true)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,15 +27,15 @@ func GetAccentColor(env platform.Environment) (*RGB, error) {
|
||||||
func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) {
|
func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) {
|
||||||
rgb, err := GetAccentColor(env)
|
rgb, err := GetAccentColor(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.accent = &cachedColor{
|
d.accent = &Colors{
|
||||||
Foreground: string(d.AnsiColorFromString(defaultColor, false)),
|
Foreground: string(d.ToColor(defaultColor, false)),
|
||||||
Background: string(d.AnsiColorFromString(defaultColor, true)),
|
Background: string(d.ToColor(defaultColor, true)),
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
foreground := color.RGB(rgb.R, rgb.G, rgb.B, false)
|
foreground := color.RGB(rgb.R, rgb.G, rgb.B, false)
|
||||||
background := color.RGB(rgb.R, rgb.G, rgb.B, true)
|
background := color.RGB(rgb.R, rgb.G, rgb.B, true)
|
||||||
d.accent = &cachedColor{
|
d.accent = &Colors{
|
||||||
Foreground: foreground.String(),
|
Foreground: foreground.String(),
|
||||||
Background: background.String(),
|
Background: background.String(),
|
||||||
}
|
}
|
||||||
|
|
10
src/ansi/cycle.go
Normal file
10
src/ansi/cycle.go
Normal file
|
@ -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:]
|
||||||
|
}
|
|
@ -55,11 +55,13 @@ type Block struct {
|
||||||
writer *ansi.Writer
|
writer *ansi.Writer
|
||||||
activeSegment *Segment
|
activeSegment *Segment
|
||||||
previousActiveSegment *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.env = env
|
||||||
b.writer = writer
|
b.writer = writer
|
||||||
|
b.cycle = cycle
|
||||||
b.executeSegmentLogic()
|
b.executeSegmentLogic()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,6 +132,11 @@ func (b *Block) RenderSegments() (string, int) {
|
||||||
if !segment.Enabled && segment.style() != Accordion {
|
if !segment.Enabled && segment.style() != Accordion {
|
||||||
continue
|
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.setActiveSegment(segment)
|
||||||
b.renderActiveSegment()
|
b.renderActiveSegment()
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ type Config struct {
|
||||||
DebugPrompt *Segment `json:"debug_prompt,omitempty"`
|
DebugPrompt *Segment `json:"debug_prompt,omitempty"`
|
||||||
Palette ansi.Palette `json:"palette,omitempty"`
|
Palette ansi.Palette `json:"palette,omitempty"`
|
||||||
Palettes *ansi.Palettes `json:"palettes,omitempty"`
|
Palettes *ansi.Palettes `json:"palettes,omitempty"`
|
||||||
|
Cycle ansi.Cycle `json:"cycle,omitempty"`
|
||||||
PWD string `json:"pwd,omitempty"`
|
PWD string `json:"pwd,omitempty"`
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
@ -63,7 +64,7 @@ type Config struct {
|
||||||
|
|
||||||
// MakeColors creates instance of AnsiColors to use in AnsiWriter according to
|
// MakeColors creates instance of AnsiColors to use in AnsiWriter according to
|
||||||
// environment and configuration.
|
// environment and configuration.
|
||||||
func (cfg *Config) MakeColors() ansi.Colors {
|
func (cfg *Config) MakeColors() ansi.ColorString {
|
||||||
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1"
|
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1"
|
||||||
return ansi.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env)
|
return ansi.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ type Engine struct {
|
||||||
currentLineLength int
|
currentLineLength int
|
||||||
rprompt string
|
rprompt string
|
||||||
rpromptLength int
|
rpromptLength int
|
||||||
|
cycle *ansi.Cycle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) write(text string) {
|
func (e *Engine) write(text string) {
|
||||||
|
@ -57,6 +58,8 @@ func (e *Engine) canWriteRightBlock(rprompt bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) PrintPrimary() string {
|
func (e *Engine) PrintPrimary() string {
|
||||||
|
// cache a pointer to the color cycle
|
||||||
|
e.cycle = &e.Config.Cycle
|
||||||
for _, block := range e.Config.Blocks {
|
for _, block := range e.Config.Blocks {
|
||||||
e.renderBlock(block)
|
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) {
|
if e.Env.Shell() == shell.BASH && (block.Type == RPrompt || block.Alignment == Right) {
|
||||||
block.InitPlain(e.Env, e.Config)
|
block.InitPlain(e.Env, e.Config)
|
||||||
} else {
|
} else {
|
||||||
block.Init(e.Env, e.Writer)
|
block.Init(e.Env, e.Writer, e.cycle)
|
||||||
}
|
}
|
||||||
if !block.Enabled() {
|
if !block.Enabled() {
|
||||||
return
|
return
|
||||||
|
@ -243,9 +246,11 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string {
|
||||||
duration: time.Since(titleStartTime),
|
duration: time.Since(titleStartTime),
|
||||||
}
|
}
|
||||||
segmentTimings = append(segmentTimings, segmentTiming)
|
segmentTimings = append(segmentTimings, segmentTiming)
|
||||||
|
// cache a pointer to the color cycle
|
||||||
|
e.cycle = &e.Config.Cycle
|
||||||
// loop each segments of each blocks
|
// loop each segments of each blocks
|
||||||
for _, block := range e.Config.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()
|
longestSegmentName, timings := block.Debug()
|
||||||
segmentTimings = append(segmentTimings, timings...)
|
segmentTimings = append(segmentTimings, timings...)
|
||||||
if longestSegmentName > largestSegmentNameLength {
|
if longestSegmentName > largestSegmentNameLength {
|
||||||
|
@ -344,7 +349,7 @@ func (e *Engine) PrintTooltip(tip string) string {
|
||||||
}
|
}
|
||||||
switch e.Env.Shell() {
|
switch e.Env.Shell() {
|
||||||
case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC:
|
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() {
|
if !block.Enabled() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -453,7 +458,7 @@ func (e *Engine) PrintRPrompt() string {
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
block.Init(e.Env, e.Writer)
|
block.Init(e.Env, e.Writer, e.cycle)
|
||||||
if !block.Enabled() {
|
if !block.Enabled() {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jandedobbeleer/oh-my-posh/src/ansi"
|
||||||
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
"github.com/jandedobbeleer/oh-my-posh/src/platform"
|
||||||
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
"github.com/jandedobbeleer/oh-my-posh/src/properties"
|
||||||
"github.com/jandedobbeleer/oh-my-posh/src/segments"
|
"github.com/jandedobbeleer/oh-my-posh/src/segments"
|
||||||
|
@ -39,13 +40,13 @@ type Segment struct {
|
||||||
MaxWidth int `json:"max_width,omitempty"`
|
MaxWidth int `json:"max_width,omitempty"`
|
||||||
MinWidth int `json:"min_width,omitempty"`
|
MinWidth int `json:"min_width,omitempty"`
|
||||||
|
|
||||||
env platform.Environment
|
Enabled bool `json:"-"`
|
||||||
writer SegmentWriter
|
|
||||||
Enabled bool `json:"-"`
|
colors *ansi.Colors
|
||||||
text string
|
env platform.Environment
|
||||||
backgroundCache string
|
writer SegmentWriter
|
||||||
foregroundCache string
|
text string
|
||||||
styleCache SegmentStyle
|
styleCache SegmentStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
// SegmentTiming holds the timing context for a segment
|
// SegmentTiming holds the timing context for a segment
|
||||||
|
@ -360,17 +361,23 @@ func (segment *Segment) shouldInvokeWithTip(tip string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (segment *Segment) foreground() string {
|
func (segment *Segment) foreground() string {
|
||||||
if len(segment.foregroundCache) == 0 {
|
if segment.colors == nil {
|
||||||
segment.foregroundCache = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground)
|
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 {
|
func (segment *Segment) background() string {
|
||||||
if len(segment.backgroundCache) == 0 {
|
if segment.colors == nil {
|
||||||
segment.backgroundCache = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background)
|
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 {
|
func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error {
|
||||||
|
|
|
@ -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": {
|
"accent_color": {
|
||||||
"title": "Accent color",
|
"title": "Accent color",
|
||||||
"$ref": "#/definitions/color"
|
"$ref": "#/definitions/color"
|
||||||
|
|
|
@ -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`
|
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.
|
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/
|
[hexcolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
|
||||||
[ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
|
[ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/
|
||||||
[git]: /docs/segments/git
|
[git]: /docs/segments/git
|
||||||
|
|
Loading…
Reference in a new issue