mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-21 02:55:37 -08:00
feat(palette): a map of named color values
introducing a map of named standard color values that can be referenced in theme segments
This commit is contained in:
parent
862d37bb7b
commit
9ecd7c09a4
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
|
@ -2,7 +2,7 @@
|
||||||
"go.lintTool": "golangci-lint",
|
"go.lintTool": "golangci-lint",
|
||||||
"go.useLanguageServer": true,
|
"go.useLanguageServer": true,
|
||||||
"go.languageServerExperimentalFeatures": {
|
"go.languageServerExperimentalFeatures": {
|
||||||
"diagnostics": false,
|
"diagnostics": false
|
||||||
},
|
},
|
||||||
"go.lintFlags": ["--fast"],
|
"go.lintFlags": ["--fast"],
|
||||||
"go.testOnSave": true,
|
"go.testOnSave": true,
|
||||||
|
@ -13,7 +13,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"go.formatTool": "gofmt",
|
"go.formatTool": "gofmt",
|
||||||
"go.formatFlags": [
|
"go.formatFlags": ["-s"]
|
||||||
"-s"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
35
.vscode/tasks.json
vendored
35
.vscode/tasks.json
vendored
|
@ -30,14 +30,45 @@
|
||||||
},
|
},
|
||||||
"statusbar": {
|
"statusbar": {
|
||||||
"hide": false,
|
"hide": false,
|
||||||
"color" : "#22C1D6",
|
"color": "#22C1D6",
|
||||||
"label" : "$(beaker) devcontainer: build omp",
|
"label": "$(beaker) devcontainer: build omp",
|
||||||
"tooltip": "Compiles *oh-my-posh* from this repo while **overwriting** your preinstalled stable release."
|
"tooltip": "Compiles *oh-my-posh* from this repo while **overwriting** your preinstalled stable release."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"group": "build",
|
"group": "build",
|
||||||
"problemMatcher": "$go",
|
"problemMatcher": "$go",
|
||||||
"args": ["build", "-v", "-o", "`readlink", "/usr/bin/oh-my-posh`"]
|
"args": ["build", "-v", "-o", "`readlink", "/usr/bin/oh-my-posh`"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "golangci-lint - docker",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker",
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"focus": false,
|
||||||
|
"echo": true,
|
||||||
|
"showReuseMessage": false,
|
||||||
|
"panel": "dedicated",
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"base": "$go",
|
||||||
|
"fileLocation": ["relative", "${workspaceFolder}/src"]
|
||||||
|
},
|
||||||
|
"args": [
|
||||||
|
"run", "--rm", "-v", "${workspaceFolder}/src:/app", "-w", "/app",
|
||||||
|
"golangci/golangci-lint:${input:golangci-lint-version}",
|
||||||
|
"golangci-lint", "run", "-v"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"id": "golangci-lint-version",
|
||||||
|
"description": "Version of the golangci-lint Docker container to use in lint task.",
|
||||||
|
"type": "promptString",
|
||||||
|
"default": "v1.43.0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,5 +51,121 @@ To change *only* the background color, just omit the first color from the above
|
||||||
"prefix": "<,#FFFFFF>┏[</>",
|
"prefix": "<,#FFFFFF>┏[</>",
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Palette
|
||||||
|
|
||||||
|
If your theme defined the Palette, you can use the _Palette reference_ `p:<palette key>` in places where the
|
||||||
|
__Standard color__ is expected.
|
||||||
|
|
||||||
|
### Defining a Palette
|
||||||
|
|
||||||
|
Palette is a set of named __Standard colors__. To use a Palette, define a `"palette"` object
|
||||||
|
at the top level of your theme:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
|
||||||
|
"palette": {
|
||||||
|
"git-foreground": "#193549",
|
||||||
|
"git": "#FFFB38",
|
||||||
|
"git-modified": "#FF9248",
|
||||||
|
"git-diverged": "#FF4500",
|
||||||
|
"git-ahead": "#B388FF",
|
||||||
|
"git-behind": "#B388FF",
|
||||||
|
"red": "#FF0000",
|
||||||
|
"green": "#00FF00",
|
||||||
|
"blue": "#0000FF",
|
||||||
|
"white": "#FFFFFF",
|
||||||
|
"black": "#111111"
|
||||||
|
},
|
||||||
|
"blocks": {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Color names (palette keys) can have any string value, so be creative.
|
||||||
|
Color values, on the other hand, should adhere to the __Standard color__ format.
|
||||||
|
|
||||||
|
### Using a Palette
|
||||||
|
|
||||||
|
You can now _Palette references_ in any [Segment's][segment] `foreground`, `foreground_templates`,
|
||||||
|
`background`, `background_templates` properties, and other config properties that expect __Standard color__ value.
|
||||||
|
_Palette reference_ format is `p:<palette key>`. Take a look at the [Git][git] segment using _Palette references_:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "p:git-foreground",
|
||||||
|
"background": "p:git",
|
||||||
|
"background_templates": [
|
||||||
|
"{{ if or (.Working.Changed) (.Staging.Changed) }}p:git-modified{{ end }}",
|
||||||
|
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:git-diverged{{ end }}",
|
||||||
|
"{{ if gt .Ahead 0 }}p:git-ahead{{ end }}",
|
||||||
|
"{{ if gt .Behind 0 }}p:git-behind{{ end }}"
|
||||||
|
],
|
||||||
|
...
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
Having all of the colors defined in one place allows you to import existing color themes (usually with slight
|
||||||
|
tweaking to adhere to the format), easily change colors of multiple segments at once, and have a more
|
||||||
|
organized theme overall. Be creative!
|
||||||
|
|
||||||
|
### _Palette references_ and __Standard colors__
|
||||||
|
|
||||||
|
Using Palette does not interfere with using __Standard colors__ in your theme. You can still use __Standard colors__
|
||||||
|
everywhere. This can be useful if you want to use a specific color for a single segment element, or in a
|
||||||
|
_Color override_ ([Battery segment][battery]):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "battery",
|
||||||
|
"style": "powerline",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"powerline_symbol": "\uE0B2",
|
||||||
|
"foreground": "p:white",
|
||||||
|
"background": "p:black",
|
||||||
|
"properties": {
|
||||||
|
"battery_icon": "<#ffa500> [II ]- </>", // icon should always be orange
|
||||||
|
"discharging_icon": "- ",
|
||||||
|
"charging_icon": "+ ",
|
||||||
|
"charged_icon": "* ",
|
||||||
|
"color_background": true,
|
||||||
|
"charged_color": "#4caf50", //
|
||||||
|
"charging_color": "#40c4ff", // battery should use specific colors for status
|
||||||
|
"discharging_color": "#ff5722", //
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Handling of invalid references
|
||||||
|
|
||||||
|
Should you use an invalid _Palette reference_ as a color (for example typo `p:bleu` instead of `p:blue`),
|
||||||
|
the Pallete engine will use the Transparent keyword as a fallback value. So if you see your prompt segments
|
||||||
|
rendered with incorrect colors, and you are using a Palette, be sure to check the correctness of your references.
|
||||||
|
|
||||||
|
### Recursive resolution
|
||||||
|
|
||||||
|
Palette allows for recursive _Palette reference_ resolution. You can use a _Palette reference_ as a color
|
||||||
|
value in Palette. This allows you to define named colors, and use references to those colors as Palette values.
|
||||||
|
For example, `p:foreground` and `p:background` will be correctly set to "#CAF0F80" and "#023E8A":
|
||||||
|
|
||||||
|
```json
|
||||||
|
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
|
||||||
|
"palette": {
|
||||||
|
"light-blue": "#CAF0F8",
|
||||||
|
"dark-blue": "#023E8A",
|
||||||
|
"foreground": "p:light-blue",
|
||||||
|
"background": "p:dark-blue"
|
||||||
|
},
|
||||||
|
"blocks": {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
[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/segment-git
|
||||||
|
[battery]: /docs/segment-battery
|
||||||
|
|
133
src/colors.go
Normal file
133
src/colors.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gookit/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MakeColors creates instance of AnsiColors to use in AnsiWriter according to
|
||||||
|
// environment and configuration.
|
||||||
|
func MakeColors(env environmentInfo, cfg *Config) AnsiColors {
|
||||||
|
cacheDisabled := env.getenv("OMP_CACHE_DISABLED") == "1"
|
||||||
|
return makeColors(cfg.Palette, !cacheDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeColors(palette Palette, cacheEnabled bool) (colors AnsiColors) {
|
||||||
|
colors = &DefaultColors{}
|
||||||
|
if palette != nil {
|
||||||
|
colors = &PaletteColors{ansiColors: colors, palette: palette}
|
||||||
|
}
|
||||||
|
if cacheEnabled {
|
||||||
|
colors = &CachedColors{ansiColors: colors}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultColors is the default AnsiColors implementation.
|
||||||
|
type DefaultColors struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Map for color names and their respective foreground [0] or background [1] color codes
|
||||||
|
ansiColorCodes = map[string][2]AnsiColor{
|
||||||
|
"black": {"30", "40"},
|
||||||
|
"red": {"31", "41"},
|
||||||
|
"green": {"32", "42"},
|
||||||
|
"yellow": {"33", "43"},
|
||||||
|
"blue": {"34", "44"},
|
||||||
|
"magenta": {"35", "45"},
|
||||||
|
"cyan": {"36", "46"},
|
||||||
|
"white": {"37", "47"},
|
||||||
|
"default": {"39", "49"},
|
||||||
|
"darkGray": {"90", "100"},
|
||||||
|
"lightRed": {"91", "101"},
|
||||||
|
"lightGreen": {"92", "102"},
|
||||||
|
"lightYellow": {"93", "103"},
|
||||||
|
"lightBlue": {"94", "104"},
|
||||||
|
"lightMagenta": {"95", "105"},
|
||||||
|
"lightCyan": {"96", "106"},
|
||||||
|
"lightWhite": {"97", "107"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
foregroundIndex = 0
|
||||||
|
backgroundIndex = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*DefaultColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
|
||||||
|
if len(colorString) == 0 {
|
||||||
|
return emptyAnsiColor
|
||||||
|
}
|
||||||
|
if colorString == Transparent {
|
||||||
|
return transparentAnsiColor
|
||||||
|
}
|
||||||
|
colorFromName, err := getAnsiColorFromName(colorString, isBackground)
|
||||||
|
if err == nil {
|
||||||
|
return colorFromName
|
||||||
|
}
|
||||||
|
style := color.HEX(colorString, isBackground)
|
||||||
|
if style.IsEmpty() {
|
||||||
|
return emptyAnsiColor
|
||||||
|
}
|
||||||
|
return AnsiColor(style.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAnsiColorFromName returns the color code for a given color name if the name is
|
||||||
|
// known ANSI color name.
|
||||||
|
func getAnsiColorFromName(colorName string, isBackground bool) (AnsiColor, error) {
|
||||||
|
if colorCodes, found := ansiColorCodes[colorName]; found {
|
||||||
|
if isBackground {
|
||||||
|
return colorCodes[backgroundIndex], nil
|
||||||
|
}
|
||||||
|
return colorCodes[foregroundIndex], nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("color name %s does not exist", colorName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAnsiColorName(colorString string) bool {
|
||||||
|
_, ok := ansiColorCodes[colorString]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaletteColors is the AnsiColors Decorator that uses the Palette to do named color
|
||||||
|
// lookups before ANSI color code generation.
|
||||||
|
type PaletteColors struct {
|
||||||
|
ansiColors AnsiColors
|
||||||
|
palette Palette
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PaletteColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
|
||||||
|
paletteColor, err := p.palette.ResolveColor(colorString)
|
||||||
|
if err != nil {
|
||||||
|
return emptyAnsiColor
|
||||||
|
}
|
||||||
|
ansiColor := p.ansiColors.AnsiColorFromString(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
|
||||||
|
// has measurable positive effect on performance.
|
||||||
|
type CachedColors struct {
|
||||||
|
ansiColors AnsiColors
|
||||||
|
colorCache map[cachedColorKey]AnsiColor
|
||||||
|
}
|
||||||
|
|
||||||
|
type cachedColorKey struct {
|
||||||
|
colorString string
|
||||||
|
isBackground bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CachedColors) AnsiColorFromString(colorString string, isBackground bool) AnsiColor {
|
||||||
|
if c.colorCache == nil {
|
||||||
|
c.colorCache = make(map[cachedColorKey]AnsiColor)
|
||||||
|
}
|
||||||
|
key := cachedColorKey{colorString, isBackground}
|
||||||
|
if ansiColor, hit := c.colorCache[key]; hit {
|
||||||
|
return ansiColor
|
||||||
|
}
|
||||||
|
ansiColor := c.ansiColors.AnsiColorFromString(colorString, isBackground)
|
||||||
|
c.colorCache[key] = ansiColor
|
||||||
|
return ansiColor
|
||||||
|
}
|
58
src/colors_test.go
Normal file
58
src/colors_test.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetAnsiFromColorString(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Case string
|
||||||
|
Expected AnsiColor
|
||||||
|
Color string
|
||||||
|
Background bool
|
||||||
|
}{
|
||||||
|
{Case: "Invalid background", Expected: emptyAnsiColor, Color: "invalid", Background: true},
|
||||||
|
{Case: "Invalid background", Expected: emptyAnsiColor, Color: "invalid", Background: false},
|
||||||
|
{Case: "Hex foreground", Expected: AnsiColor("38;2;170;187;204"), Color: "#AABBCC", Background: false},
|
||||||
|
{Case: "Hex backgrond", Expected: AnsiColor("48;2;170;187;204"), Color: "#AABBCC", Background: true},
|
||||||
|
{Case: "Base 8 foreground", Expected: AnsiColor("31"), Color: "red", Background: false},
|
||||||
|
{Case: "Base 8 background", Expected: AnsiColor("41"), Color: "red", Background: true},
|
||||||
|
{Case: "Base 16 foreground", Expected: AnsiColor("91"), Color: "lightRed", Background: false},
|
||||||
|
{Case: "Base 16 backround", Expected: AnsiColor("101"), Color: "lightRed", Background: true},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
ansiColors := &DefaultColors{}
|
||||||
|
ansiColor := ansiColors.AnsiColorFromString(tc.Color, tc.Background)
|
||||||
|
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeColors(t *testing.T) {
|
||||||
|
colors := makeColors(nil, false)
|
||||||
|
assert.IsType(t, &DefaultColors{}, colors)
|
||||||
|
|
||||||
|
colors = makeColors(nil, true)
|
||||||
|
assert.IsType(t, &CachedColors{}, colors)
|
||||||
|
assert.IsType(t, &DefaultColors{}, colors.(*CachedColors).ansiColors)
|
||||||
|
|
||||||
|
colors = makeColors(testPalette, false)
|
||||||
|
assert.IsType(t, &PaletteColors{}, colors)
|
||||||
|
assert.IsType(t, &DefaultColors{}, colors.(*PaletteColors).ansiColors)
|
||||||
|
|
||||||
|
colors = makeColors(testPalette, true)
|
||||||
|
assert.IsType(t, &CachedColors{}, colors)
|
||||||
|
assert.IsType(t, &PaletteColors{}, colors.(*CachedColors).ansiColors)
|
||||||
|
assert.IsType(t, &DefaultColors{}, colors.(*CachedColors).ansiColors.(*PaletteColors).ansiColors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEngineRenderPalette(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = engineRender("jandedobbeleer-palette.omp.json")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ type Config struct {
|
||||||
Blocks []*Block `config:"blocks"`
|
Blocks []*Block `config:"blocks"`
|
||||||
Tooltips []*Segment `config:"tooltips"`
|
Tooltips []*Segment `config:"tooltips"`
|
||||||
TransientPrompt *TransientPrompt `config:"transient_prompt"`
|
TransientPrompt *TransientPrompt `config:"transient_prompt"`
|
||||||
|
Palette Palette `config:"palette"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransientPrompt struct {
|
type TransientPrompt struct {
|
||||||
|
|
|
@ -3,11 +3,17 @@ package main
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gookit/config/v2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSettingsExportJSON(t *testing.T) {
|
func TestSettingsExportJSON(t *testing.T) {
|
||||||
|
defer testClearDefaultConfig()
|
||||||
content := exportConfig("../themes/jandedobbeleer.omp.json", "json")
|
content := exportConfig("../themes/jandedobbeleer.omp.json", "json")
|
||||||
assert.NotContains(t, content, "\\u003ctransparent\\u003e")
|
assert.NotContains(t, content, "\\u003ctransparent\\u003e")
|
||||||
assert.Contains(t, content, "<transparent>")
|
assert.Contains(t, content, "<transparent>")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testClearDefaultConfig() {
|
||||||
|
config.Default().ClearAll()
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -41,3 +43,78 @@ func TestCanWriteRPrompt(t *testing.T) {
|
||||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkEngineRender(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = engineRender("jandedobbeleer.omp.json")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func engineRender(configPath string) error {
|
||||||
|
testDir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath = filepath.Join(testDir, "testdata", configPath)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = false
|
||||||
|
eval = false
|
||||||
|
shell = "pwsh"
|
||||||
|
plain = false
|
||||||
|
pwd = ""
|
||||||
|
pswd = ""
|
||||||
|
code = 2
|
||||||
|
execTime = 917.0
|
||||||
|
)
|
||||||
|
|
||||||
|
args := &args{
|
||||||
|
Debug: &debug,
|
||||||
|
Config: &configPath,
|
||||||
|
Eval: &eval,
|
||||||
|
Shell: &shell,
|
||||||
|
Plain: &plain,
|
||||||
|
PWD: &pwd,
|
||||||
|
PSWD: &pswd,
|
||||||
|
ErrorCode: &code,
|
||||||
|
ExecutionTime: &execTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
env := &environment{}
|
||||||
|
env.init(args)
|
||||||
|
defer env.close()
|
||||||
|
|
||||||
|
cfg := GetConfig(env)
|
||||||
|
defer testClearDefaultConfig()
|
||||||
|
|
||||||
|
ansi := &ansiUtils{}
|
||||||
|
ansi.init(env.getShellName())
|
||||||
|
writerColors := MakeColors(env, cfg)
|
||||||
|
writer := &AnsiWriter{
|
||||||
|
ansi: ansi,
|
||||||
|
terminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground),
|
||||||
|
ansiColors: writerColors,
|
||||||
|
}
|
||||||
|
title := &consoleTitle{
|
||||||
|
env: env,
|
||||||
|
config: cfg,
|
||||||
|
ansi: ansi,
|
||||||
|
}
|
||||||
|
engine := &engine{
|
||||||
|
config: cfg,
|
||||||
|
env: env,
|
||||||
|
writer: writer,
|
||||||
|
consoleTitle: title,
|
||||||
|
ansi: ansi,
|
||||||
|
plain: *args.Plain,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.render()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -209,9 +209,11 @@ func main() {
|
||||||
if *args.Plain {
|
if *args.Plain {
|
||||||
writer = &PlainWriter{}
|
writer = &PlainWriter{}
|
||||||
} else {
|
} else {
|
||||||
|
writerColors := MakeColors(env, cfg)
|
||||||
writer = &AnsiWriter{
|
writer = &AnsiWriter{
|
||||||
ansi: ansi,
|
ansi: ansi,
|
||||||
terminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground),
|
terminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground),
|
||||||
|
ansiColors: writerColors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
title := &consoleTitle{
|
title := &consoleTitle{
|
||||||
|
|
102
src/palette.go
Normal file
102
src/palette.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Palette map[string]string
|
||||||
|
|
||||||
|
const (
|
||||||
|
paletteKeyPrefix = "p:"
|
||||||
|
paletteKeyError = "palette: requested color %s does not exist in palette of colors %s"
|
||||||
|
paletteMaxRecursionDepth = 3 // allows 3 or less recusive resolutions
|
||||||
|
paletteRecursiveKeyError = "palette: recursive resolution of color %s returned palette reference %s and reached recursion depth %d"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolveColor gets a color value from the palette using given colorName.
|
||||||
|
// If colorName is not a palette reference, it is returned as is.
|
||||||
|
func (p Palette) ResolveColor(colorName string) (string, error) {
|
||||||
|
return p.resolveColor(colorName, 1, &colorName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// originalColorName is a pointer to save allocations
|
||||||
|
func (p Palette) resolveColor(colorName string, depth int, originalColorName *string) (string, error) {
|
||||||
|
key, ok := asPaletteKey(colorName)
|
||||||
|
// colorName is not a palette key, return it as is
|
||||||
|
if !ok {
|
||||||
|
return colorName, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
color, ok := p[key]
|
||||||
|
if !ok {
|
||||||
|
return "", &PaletteKeyError{Key: key, palette: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, isKey := isPaletteKey(color); isKey {
|
||||||
|
if depth > paletteMaxRecursionDepth {
|
||||||
|
return "", &PaletteRecursiveKeyError{Key: *originalColorName, Value: color, depth: depth}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.resolveColor(color, depth+1, originalColorName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return color, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func asPaletteKey(colorName string) (string, bool) {
|
||||||
|
prefix, isKey := isPaletteKey(colorName)
|
||||||
|
if !isKey {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimPrefix(colorName, prefix)
|
||||||
|
|
||||||
|
return key, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPaletteKey(colorName string) (string, bool) {
|
||||||
|
return paletteKeyPrefix, strings.HasPrefix(colorName, paletteKeyPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaletteKeyError records the missing Palette key.
|
||||||
|
type PaletteKeyError struct {
|
||||||
|
Key string
|
||||||
|
palette Palette
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PaletteKeyError) Error() string {
|
||||||
|
keys := make([]string, 0, len(p.palette))
|
||||||
|
for key := range p.palette {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
allColors := strings.Join(keys, ",")
|
||||||
|
errorStr := fmt.Sprintf(paletteKeyError, p.Key, allColors)
|
||||||
|
return errorStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaletteRecursiveKeyError records the Palette key and resolved color value (which
|
||||||
|
// is also a Palette key)
|
||||||
|
type PaletteRecursiveKeyError struct {
|
||||||
|
Key string
|
||||||
|
Value string
|
||||||
|
depth int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PaletteRecursiveKeyError) Error() string {
|
||||||
|
errorStr := fmt.Sprintf(paletteRecursiveKeyError, p.Key, p.Value, p.depth)
|
||||||
|
return errorStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybeResolveColor wraps resolveColor and silences possible errors, returning
|
||||||
|
// Transparent color by default, as a Block does not know how to handle color errors.
|
||||||
|
func (p Palette) MaybeResolveColor(colorName string) string {
|
||||||
|
color, err := p.ResolveColor(colorName)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
|
}
|
233
src/palette_test.go
Normal file
233
src/palette_test.go
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alecthomas/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testPalette = Palette{
|
||||||
|
"red": "#FF0000",
|
||||||
|
"green": "#00FF00",
|
||||||
|
"blue": "#0000FF",
|
||||||
|
"white": "#FFFFFF",
|
||||||
|
"black": "#000000",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestPaletteRequest struct {
|
||||||
|
Case string
|
||||||
|
Request string
|
||||||
|
ExpectedError bool
|
||||||
|
Expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldResolveColorFromTestPalette(t *testing.T) {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{Case: "Palette red", Request: "p:red", Expected: "#FF0000"},
|
||||||
|
{Case: "Palette green", Request: "p:green", Expected: "#00FF00"},
|
||||||
|
{Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"},
|
||||||
|
{Case: "Palette white", Request: "p:white", Expected: "#FFFFFF"},
|
||||||
|
{Case: "Palette black", Request: "p:black", Expected: "#000000"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
testPaletteRequest(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPaletteRequest(t *testing.T, tc TestPaletteRequest) {
|
||||||
|
actual, err := testPalette.ResolveColor(tc.Request)
|
||||||
|
|
||||||
|
if !tc.ExpectedError {
|
||||||
|
assert.Nil(t, err, tc.Case)
|
||||||
|
assert.Equal(t, tc.Expected, actual, "expected different color value")
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, err, tc.Case)
|
||||||
|
assert.Equal(t, tc.Expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldIgnoreNonPaletteColors(t *testing.T) {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{Case: "Deep puprple", Request: "#1F1137", Expected: "#1F1137"},
|
||||||
|
{Case: "Light red", Request: "#D55252", Expected: "#D55252"},
|
||||||
|
{Case: "ANSI black", Request: "black", Expected: "black"},
|
||||||
|
{Case: "Foreground", Request: "foreground", Expected: "foreground"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
testPaletteRequest(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldReturnErrorOnMissingColor(t *testing.T) {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{
|
||||||
|
Case: "Palette deep purple",
|
||||||
|
Request: "p:deep-purple",
|
||||||
|
ExpectedError: true,
|
||||||
|
Expected: "palette: requested color deep-purple does not exist in palette of colors black,blue,green,red,white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette cyan",
|
||||||
|
Request: "p:cyan",
|
||||||
|
ExpectedError: true,
|
||||||
|
Expected: "palette: requested color cyan does not exist in palette of colors black,blue,green,red,white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette foreground",
|
||||||
|
Request: "p:foreground",
|
||||||
|
ExpectedError: true,
|
||||||
|
Expected: "palette: requested color foreground does not exist in palette of colors black,blue,green,red,white",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
testPaletteRequest(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldHandleMixedCases(t *testing.T) {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{Case: "Palette red", Request: "p:red", Expected: "#FF0000"},
|
||||||
|
{Case: "ANSI black", Request: "black", Expected: "black"},
|
||||||
|
{Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"},
|
||||||
|
{Case: "Palette black", Request: "p:black", Expected: "#000000"},
|
||||||
|
{Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
testPaletteRequest(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldUseEmptyColorByDefault(t *testing.T) {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{Case: "Palette magenta", Request: "p:magenta", Expected: ""},
|
||||||
|
{Case: "Palette gray", Request: "p:gray", Expected: ""},
|
||||||
|
{Case: "Palette rose", Request: "p:rose", Expected: ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
actual := testPalette.MaybeResolveColor(tc.Request)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.Expected, actual, "expected different color value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldResolveRecursiveReference(t *testing.T) {
|
||||||
|
tp := Palette{
|
||||||
|
"light-blue": "#CAF0F8",
|
||||||
|
"dark-blue": "#023E8A",
|
||||||
|
"foreground": "p:light-blue",
|
||||||
|
"background": "p:dark-blue",
|
||||||
|
"text": "p:foreground",
|
||||||
|
"icon": "p:background",
|
||||||
|
"void": "p:void", // infinite recursion - error
|
||||||
|
"1": "white",
|
||||||
|
"2": "p:1",
|
||||||
|
"3": "p:2",
|
||||||
|
"4": "p:3", // 3 recursive lookups - allowed
|
||||||
|
"5": "p:4", // 4 recursive lookups - error
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{
|
||||||
|
Case: "Palette light-blue",
|
||||||
|
Request: "p:light-blue",
|
||||||
|
Expected: "#CAF0F8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette foreground",
|
||||||
|
Request: "p:foreground",
|
||||||
|
Expected: "#CAF0F8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette background",
|
||||||
|
Request: "p:background",
|
||||||
|
Expected: "#023E8A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette text (2 recursive lookups)",
|
||||||
|
Request: "p:text",
|
||||||
|
Expected: "#CAF0F8",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette icon (2 recursive lookups)",
|
||||||
|
Request: "p:icon",
|
||||||
|
Expected: "#023E8A",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette void (infinite recursion)",
|
||||||
|
Request: "p:void",
|
||||||
|
ExpectedError: true,
|
||||||
|
Expected: "palette: recursive resolution of color p:void returned palette reference p:void and reached recursion depth 4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette p:4 (3 recursive lookups)",
|
||||||
|
Request: "p:4",
|
||||||
|
Expected: "white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "Palette p:5 (4 recursive lookups)",
|
||||||
|
Request: "p:5",
|
||||||
|
ExpectedError: true,
|
||||||
|
Expected: "palette: recursive resolution of color p:5 returned palette reference p:1 and reached recursion depth 4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
actual, err := tp.ResolveColor(tc.Request)
|
||||||
|
|
||||||
|
if !tc.ExpectedError {
|
||||||
|
assert.Nil(t, err, "expected no error")
|
||||||
|
assert.Equal(t, tc.Expected, actual, "expected different color value")
|
||||||
|
} else {
|
||||||
|
assert.NotNil(t, err, "expected error")
|
||||||
|
assert.Equal(t, tc.Expected, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaletteShouldHandleEmptyKey(t *testing.T) {
|
||||||
|
tp := Palette{
|
||||||
|
"": "#000000",
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := tp.ResolveColor("p:")
|
||||||
|
|
||||||
|
assert.Nil(t, err, "expected no error")
|
||||||
|
assert.Equal(t, "#000000", actual, "expected different color value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPaletteMixedCaseResolution(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
benchmarkPaletteMixedCaseResolution()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkPaletteMixedCaseResolution() {
|
||||||
|
cases := []TestPaletteRequest{
|
||||||
|
{Case: "Palette red", Request: "p:red", Expected: "#FF0000"},
|
||||||
|
{Case: "ANSI black", Request: "black", Expected: "black"},
|
||||||
|
{Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"},
|
||||||
|
{Case: "Palette black", Request: "p:black", Expected: "#000000"},
|
||||||
|
{Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"},
|
||||||
|
{Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"},
|
||||||
|
// repeating the same set to have longer benchmarks
|
||||||
|
{Case: "Palette red", Request: "p:red", Expected: "#FF0000"},
|
||||||
|
{Case: "ANSI black", Request: "black", Expected: "black"},
|
||||||
|
{Case: "Cyan", Request: "#05E6FA", Expected: "#05E6FA"},
|
||||||
|
{Case: "Palette black", Request: "p:black", Expected: "#000000"},
|
||||||
|
{Case: "Palette pink", Request: "p:pink", ExpectedError: true, Expected: "palette: requested color pink does not exist in palette of colors black,blue,green,red,white"},
|
||||||
|
{Case: "Palette blue", Request: "p:blue", Expected: "#0000FF"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
// both value and error values are irrelevant, but such assignment calms down
|
||||||
|
// golangci-lint "return value of `testPalette.ResolveColor` is not checked" error
|
||||||
|
_, _ = testPalette.ResolveColor(tc.Request)
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,11 +71,10 @@ func (p *properties) getColor(property Property, defaultValue string) string {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
colorString := parseString(val, defaultValue)
|
colorString := parseString(val, defaultValue)
|
||||||
_, err := getColorFromName(colorString, false)
|
if IsAnsiColorName(colorString) {
|
||||||
if err == nil {
|
|
||||||
return colorString
|
return colorString
|
||||||
}
|
}
|
||||||
values := findNamedRegexMatch(`(?P<color>#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})`, colorString)
|
values := findNamedRegexMatch(`(?P<color>#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|p:.*)`, colorString)
|
||||||
if values != nil && values["color"] != "" {
|
if values != nil && values["color"] != "" {
|
||||||
return values["color"]
|
return values["color"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,16 @@ func TestDefaultColorWithUnavailableProperty(t *testing.T) {
|
||||||
assert.Equal(t, expected, value)
|
assert.Equal(t, expected, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetPaletteColor(t *testing.T) {
|
||||||
|
expected := "p:red"
|
||||||
|
values := map[Property]interface{}{Background: expected}
|
||||||
|
properties := properties{
|
||||||
|
values: values,
|
||||||
|
}
|
||||||
|
value := properties.getColor(Background, "white")
|
||||||
|
assert.Equal(t, expected, value)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetBool(t *testing.T) {
|
func TestGetBool(t *testing.T) {
|
||||||
expected := true
|
expected := true
|
||||||
values := map[Property]interface{}{DisplayHost: expected}
|
values := map[Property]interface{}{DisplayHost: expected}
|
||||||
|
|
|
@ -726,6 +726,7 @@ func TestGetPwd(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseMappedLocations(t *testing.T) {
|
func TestParseMappedLocations(t *testing.T) {
|
||||||
|
defer testClearDefaultConfig()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Case string
|
Case string
|
||||||
JSON string
|
JSON string
|
||||||
|
|
271
src/testdata/jandedobbeleer-palette.omp.json
vendored
Normal file
271
src/testdata/jandedobbeleer-palette.omp.json
vendored
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
{
|
||||||
|
"palette": {
|
||||||
|
"session": "#C386F1",
|
||||||
|
"path": "#FF479C",
|
||||||
|
"git-foreground": "#193549",
|
||||||
|
"git": "#FFFB38",
|
||||||
|
"git-modified": "#FF9248",
|
||||||
|
"git-diverged": "#FF4500",
|
||||||
|
"git-ahead": "#B388FF",
|
||||||
|
"git-behind": "#B388FF",
|
||||||
|
"node": "#6CA35E",
|
||||||
|
"go": "#8ED1F7",
|
||||||
|
"julia": "#4063D8",
|
||||||
|
"python": "#FFDE57",
|
||||||
|
"ruby": "#AE1401",
|
||||||
|
"azfunc": "#FEAC19",
|
||||||
|
"aws-default": "#FFA400",
|
||||||
|
"aws-jan": "#F1184C",
|
||||||
|
"root": "#FFFF66",
|
||||||
|
"executiontime": "#83769C",
|
||||||
|
"exit": "#00897B",
|
||||||
|
"exit-red": "#E91E63",
|
||||||
|
"shell": "#0077C2",
|
||||||
|
"ytm": "#1BD760",
|
||||||
|
"battery": "#F36943",
|
||||||
|
"battery-charged": "#4CAF50",
|
||||||
|
"battery-charging": "#40C4FF",
|
||||||
|
"battery-discharging": "#FF5722",
|
||||||
|
"time": "#2E9599",
|
||||||
|
"white": "#FFFFFF",
|
||||||
|
"black": "#111111"
|
||||||
|
},
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "prompt",
|
||||||
|
"alignment": "left",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"type": "session",
|
||||||
|
"style": "diamond",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:session",
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "\uE0B0",
|
||||||
|
"properties": {
|
||||||
|
"postfix": " ",
|
||||||
|
"display_host": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:path",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " ",
|
||||||
|
"home_icon": "~",
|
||||||
|
"folder_separator_icon": " \uE0b1 ",
|
||||||
|
"style": "folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:git-foreground",
|
||||||
|
"background": "palette:git",
|
||||||
|
"background_templates": [
|
||||||
|
"{{ if or (.Working.Changed) (.Staging.Changed) }}palette:git-modified{{ end }}",
|
||||||
|
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}palette:git-diverged{{ end }}",
|
||||||
|
"{{ if gt .Ahead 0 }}palette:git-ahead{{ end }}",
|
||||||
|
"{{ if gt .Behind 0 }}palette:git-behind{{ end }}"
|
||||||
|
],
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "",
|
||||||
|
"properties": {
|
||||||
|
"fetch_status": true,
|
||||||
|
"fetch_stash_count": true,
|
||||||
|
"fetch_upstream_icon": true,
|
||||||
|
"branch_max_length": 25,
|
||||||
|
"template": "{{ .UpstreamIcon }}{{ .HEAD }}{{ .BranchStatus }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \uF692 {{ .StashCount }}{{ end }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:node",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uF898 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "go",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:black",
|
||||||
|
"background": "palette:go",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE626 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "julia",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:black",
|
||||||
|
"background": "palette:julia",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE624 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "python",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:black",
|
||||||
|
"background": "palette:python",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE235 ",
|
||||||
|
"display_version": true,
|
||||||
|
"display_mode": "files",
|
||||||
|
"display_virtual_env": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ruby",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:ruby",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE791 ",
|
||||||
|
"display_version": true,
|
||||||
|
"display_mode": "files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "azfunc",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:azfunc",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uf0e7",
|
||||||
|
"display_version": false,
|
||||||
|
"display_mode": "files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "aws",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background_templates": [
|
||||||
|
"{{if contains \"default\" .Profile}}palette:aws-default{{end}}",
|
||||||
|
"{{if contains \"jan\" .Profile}}palette:aws-jan{{end}}"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE7AD ",
|
||||||
|
"display_default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "root",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "palette:black",
|
||||||
|
"background": "palette:root",
|
||||||
|
"properties": {
|
||||||
|
"root_icon": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "executiontime",
|
||||||
|
"style": "plain",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:executiontime",
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "",
|
||||||
|
"properties": {
|
||||||
|
"always_enabled": true,
|
||||||
|
"prefix": "<transparent>\uE0B0</> \ufbab",
|
||||||
|
"postfix": "\u2800"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "exit",
|
||||||
|
"style": "diamond",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:exit",
|
||||||
|
"background_templates": [
|
||||||
|
"{{ if gt .Code 0 }}palette:exit-red{{ end }}"
|
||||||
|
],
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "\uE0B4",
|
||||||
|
"properties": {
|
||||||
|
"always_enabled": true,
|
||||||
|
"template": "\uE23A",
|
||||||
|
"prefix": "<parentBackground>\uE0B0</> "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rprompt",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"style": "plain",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:shell",
|
||||||
|
"properties": {
|
||||||
|
"prefix": "<#0077c2,transparent>\uE0B6</> ",
|
||||||
|
"postfix": " <transparent,#0077c2>\uE0B2</>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ytm",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B2",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"foreground": "palette:black",
|
||||||
|
"background": "palette:ytm",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uF167 ",
|
||||||
|
"paused_icon": " ",
|
||||||
|
"playing_icon": " "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "battery",
|
||||||
|
"style": "powerline",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"powerline_symbol": "\uE0B2",
|
||||||
|
"foreground": "palette:white",
|
||||||
|
"background": "palette:battery",
|
||||||
|
"properties": {
|
||||||
|
"battery_icon": "",
|
||||||
|
"discharging_icon": " ",
|
||||||
|
"charging_icon": " ",
|
||||||
|
"charged_icon": " ",
|
||||||
|
"color_background": true,
|
||||||
|
"charged_color": "palette:battery-charged",
|
||||||
|
"charging_color": "palette:battery-charging",
|
||||||
|
"discharging_color": "palette:battery-discharging",
|
||||||
|
"postfix": " "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "time",
|
||||||
|
"style": "diamond",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"leading_diamond": "\uE0B2",
|
||||||
|
"trailing_diamond": "\uE0B4",
|
||||||
|
"background": "palette:time",
|
||||||
|
"foreground": "palette:black"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_space": true,
|
||||||
|
"console_title": true,
|
||||||
|
"console_title_style": "template",
|
||||||
|
"console_title_template": "{{ .Shell }} in {{ .Folder }}"
|
||||||
|
}
|
238
src/testdata/jandedobbeleer.omp.json
vendored
Normal file
238
src/testdata/jandedobbeleer.omp.json
vendored
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
{
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"type": "prompt",
|
||||||
|
"alignment": "left",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"type": "session",
|
||||||
|
"style": "diamond",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#c386f1",
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "\uE0B0",
|
||||||
|
"properties": {
|
||||||
|
"postfix": " ",
|
||||||
|
"display_host": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "path",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#ff479c",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " ",
|
||||||
|
"home_icon": "~",
|
||||||
|
"folder_separator_icon": " \uE0b1 ",
|
||||||
|
"style": "folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#193549",
|
||||||
|
"background": "#fffb38",
|
||||||
|
"background_templates": [
|
||||||
|
"{{ if or (.Working.Changed) (.Staging.Changed) }}#FF9248{{ end }}",
|
||||||
|
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}#ff4500{{ end }}",
|
||||||
|
"{{ if gt .Ahead 0 }}#B388FF{{ end }}",
|
||||||
|
"{{ if gt .Behind 0 }}#B388FF{{ end }}"
|
||||||
|
],
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "",
|
||||||
|
"properties": {
|
||||||
|
"fetch_status": true,
|
||||||
|
"fetch_stash_count": true,
|
||||||
|
"fetch_upstream_icon": true,
|
||||||
|
"branch_max_length": 25,
|
||||||
|
"template": "{{ .UpstreamIcon }}{{ .HEAD }}{{ .BranchStatus }}{{ if .Working.Changed }} \uF044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uF046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \uF692 {{ .StashCount }}{{ end }}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#6CA35E",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uF898 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "go",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#111111",
|
||||||
|
"background": "#8ED1F7",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE626 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "julia",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#111111",
|
||||||
|
"background": "#4063D8",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE624 ",
|
||||||
|
"display_version": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "python",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#111111",
|
||||||
|
"background": "#FFDE57",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE235 ",
|
||||||
|
"display_version": true,
|
||||||
|
"display_mode": "files",
|
||||||
|
"display_virtual_env": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ruby",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#AE1401",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE791 ",
|
||||||
|
"display_version": true,
|
||||||
|
"display_mode": "files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "azfunc",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#FEAC19",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uf0e7",
|
||||||
|
"display_version": false,
|
||||||
|
"display_mode": "files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "aws",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background_templates": [
|
||||||
|
"{{if contains \"default\" .Profile}}#FFA400{{end}}",
|
||||||
|
"{{if contains \"jan\" .Profile}}#f1184c{{end}}"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uE7AD ",
|
||||||
|
"display_default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "root",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B0",
|
||||||
|
"foreground": "#111111",
|
||||||
|
"background": "#ffff66",
|
||||||
|
"properties": {
|
||||||
|
"root_icon": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "executiontime",
|
||||||
|
"style": "plain",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#83769c",
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "",
|
||||||
|
"properties": {
|
||||||
|
"always_enabled": true,
|
||||||
|
"prefix": "<transparent>\uE0B0</> \ufbab",
|
||||||
|
"postfix": "\u2800"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "exit",
|
||||||
|
"style": "diamond",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#00897b",
|
||||||
|
"background_templates": ["{{ if gt .Code 0 }}#e91e63{{ end }}"],
|
||||||
|
"leading_diamond": "",
|
||||||
|
"trailing_diamond": "\uE0B4",
|
||||||
|
"properties": {
|
||||||
|
"always_enabled": true,
|
||||||
|
"template": "\uE23A",
|
||||||
|
"prefix": "<parentBackground>\uE0B0</> "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rprompt",
|
||||||
|
"segments": [
|
||||||
|
{
|
||||||
|
"type": "shell",
|
||||||
|
"style": "plain",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#0077c2",
|
||||||
|
"properties": {
|
||||||
|
"prefix": "<#0077c2,transparent>\uE0B6</> ",
|
||||||
|
"postfix": " <transparent,#0077c2>\uE0B2</>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ytm",
|
||||||
|
"style": "powerline",
|
||||||
|
"powerline_symbol": "\uE0B2",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"foreground": "#111111",
|
||||||
|
"background": "#1BD760",
|
||||||
|
"properties": {
|
||||||
|
"prefix": " \uF167 ",
|
||||||
|
"paused_icon": " ",
|
||||||
|
"playing_icon": " "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "battery",
|
||||||
|
"style": "powerline",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"powerline_symbol": "\uE0B2",
|
||||||
|
"foreground": "#ffffff",
|
||||||
|
"background": "#f36943",
|
||||||
|
"properties": {
|
||||||
|
"battery_icon": "",
|
||||||
|
"discharging_icon": " ",
|
||||||
|
"charging_icon": " ",
|
||||||
|
"charged_icon": " ",
|
||||||
|
"color_background": true,
|
||||||
|
"charged_color": "#4caf50",
|
||||||
|
"charging_color": "#40c4ff",
|
||||||
|
"discharging_color": "#ff5722",
|
||||||
|
"postfix": " "
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "time",
|
||||||
|
"style": "diamond",
|
||||||
|
"invert_powerline": true,
|
||||||
|
"leading_diamond": "\uE0B2",
|
||||||
|
"trailing_diamond": "\uE0B4",
|
||||||
|
"background": "#2e9599",
|
||||||
|
"foreground": "#111111"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final_space": true,
|
||||||
|
"console_title": true,
|
||||||
|
"console_title_style": "template",
|
||||||
|
"console_title_template": "{{ .Shell }} in {{ .Folder }}"
|
||||||
|
}
|
|
@ -1,52 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gookit/color"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Map for color names and their respective foreground [0] or background [1] color codes
|
|
||||||
colorMap = map[string][2]string{
|
|
||||||
"black": {"30", "40"},
|
|
||||||
"red": {"31", "41"},
|
|
||||||
"green": {"32", "42"},
|
|
||||||
"yellow": {"33", "43"},
|
|
||||||
"blue": {"34", "44"},
|
|
||||||
"magenta": {"35", "45"},
|
|
||||||
"cyan": {"36", "46"},
|
|
||||||
"white": {"37", "47"},
|
|
||||||
"default": {"39", "49"},
|
|
||||||
"darkGray": {"90", "100"},
|
|
||||||
"lightRed": {"91", "101"},
|
|
||||||
"lightGreen": {"92", "102"},
|
|
||||||
"lightYellow": {"93", "103"},
|
|
||||||
"lightBlue": {"94", "104"},
|
|
||||||
"lightMagenta": {"95", "105"},
|
|
||||||
"lightCyan": {"96", "106"},
|
|
||||||
"lightWhite": {"97", "107"},
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
colorRegex = `<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`
|
colorRegex = `<(?P<foreground>[^,>]+)?,?(?P<background>[^>]+)?>(?P<content>[^<]*)<\/>`
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the color code for a given color name
|
|
||||||
func getColorFromName(colorName string, isBackground bool) (string, error) {
|
|
||||||
colorMapOffset := 0
|
|
||||||
if isBackground {
|
|
||||||
colorMapOffset = 1
|
|
||||||
}
|
|
||||||
if colorCodes, found := colorMap[colorName]; found {
|
|
||||||
return colorCodes[colorMapOffset], nil
|
|
||||||
}
|
|
||||||
return "", errors.New("color name does not exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
type promptWriter interface {
|
type promptWriter interface {
|
||||||
write(background, foreground, text string)
|
write(background, foreground, text string)
|
||||||
string() string
|
string() string
|
||||||
|
@ -63,6 +25,7 @@ type AnsiWriter struct {
|
||||||
terminalBackground string
|
terminalBackground string
|
||||||
Colors *Color
|
Colors *Color
|
||||||
ParentColors *Color
|
ParentColors *Color
|
||||||
|
ansiColors AnsiColors
|
||||||
}
|
}
|
||||||
|
|
||||||
type Color struct {
|
type Color struct {
|
||||||
|
@ -70,6 +33,32 @@ type Color struct {
|
||||||
Foreground string
|
Foreground string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnsiColors is the interface that wraps AnsiColorFromString method.
|
||||||
|
//
|
||||||
|
// AnsiColorFromString 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 AnsiColors interface {
|
||||||
|
AnsiColorFromString(colorString string, isBackground bool) AnsiColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnsiColor is an ANSI color code ready to be printed to the console.
|
||||||
|
// Example: "38;2;255;255;255", "48;2;255;255;255", "31", "95".
|
||||||
|
type AnsiColor string
|
||||||
|
|
||||||
|
const (
|
||||||
|
emptyAnsiColor = AnsiColor("")
|
||||||
|
transparentAnsiColor = AnsiColor(Transparent)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c AnsiColor) IsEmpty() bool {
|
||||||
|
return c == emptyAnsiColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c AnsiColor) IsTransparent() bool {
|
||||||
|
return c == transparentAnsiColor
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Transparent implies a transparent color
|
// Transparent implies a transparent color
|
||||||
Transparent = "transparent"
|
Transparent = "transparent"
|
||||||
|
@ -101,44 +90,30 @@ func (a *AnsiWriter) clearParentColors() {
|
||||||
a.ParentColors = nil
|
a.ParentColors = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the ANSI color code for a given color string.
|
func (a *AnsiWriter) getAnsiFromColorString(colorString string, isBackground bool) AnsiColor {
|
||||||
// This can include a valid hex color in the format `#FFFFFF`,
|
return a.ansiColors.AnsiColorFromString(colorString, isBackground)
|
||||||
// but also a name of one of the first 16 ANSI colors like `lightBlue`.
|
|
||||||
func (a *AnsiWriter) getAnsiFromColorString(colorString string, isBackground bool) string {
|
|
||||||
if colorString == Transparent || len(colorString) == 0 {
|
|
||||||
return colorString
|
|
||||||
}
|
|
||||||
colorFromName, err := getColorFromName(colorString, isBackground)
|
|
||||||
if err == nil {
|
|
||||||
return colorFromName
|
|
||||||
}
|
|
||||||
style := color.HEX(colorString, isBackground)
|
|
||||||
if style.IsEmpty() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return style.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AnsiWriter) writeColoredText(background, foreground, text string) {
|
func (a *AnsiWriter) writeColoredText(background, foreground AnsiColor, text string) {
|
||||||
// Avoid emitting empty strings with color codes
|
// Avoid emitting empty strings with color codes
|
||||||
if text == "" || (foreground == Transparent && background == Transparent) {
|
if text == "" || (foreground.IsTransparent() && background.IsTransparent()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// default to white fg if empty, empty backgrond is supported
|
// default to white fg if empty, empty backgrond is supported
|
||||||
if len(foreground) == 0 {
|
if foreground.IsEmpty() {
|
||||||
foreground = a.getAnsiFromColorString("white", false)
|
foreground = a.getAnsiFromColorString("white", false)
|
||||||
}
|
}
|
||||||
if foreground == Transparent && len(background) != 0 && len(a.terminalBackground) != 0 {
|
if foreground.IsTransparent() && !background.IsEmpty() && len(a.terminalBackground) != 0 {
|
||||||
fgAnsiColor := a.getAnsiFromColorString(a.terminalBackground, false)
|
fgAnsiColor := a.getAnsiFromColorString(a.terminalBackground, false)
|
||||||
coloredText := fmt.Sprintf(a.ansi.colorFull, background, fgAnsiColor, text)
|
coloredText := fmt.Sprintf(a.ansi.colorFull, background, fgAnsiColor, text)
|
||||||
a.builder.WriteString(coloredText)
|
a.builder.WriteString(coloredText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if foreground == Transparent && len(background) != 0 {
|
if foreground.IsTransparent() && !background.IsEmpty() {
|
||||||
coloredText := fmt.Sprintf(a.ansi.colorTransparent, background, text)
|
coloredText := fmt.Sprintf(a.ansi.colorTransparent, background, text)
|
||||||
a.builder.WriteString(coloredText)
|
a.builder.WriteString(coloredText)
|
||||||
return
|
return
|
||||||
} else if len(background) == 0 || background == Transparent {
|
} else if background.IsEmpty() || background.IsTransparent() {
|
||||||
coloredText := fmt.Sprintf(a.ansi.colorSingle, foreground, text)
|
coloredText := fmt.Sprintf(a.ansi.colorSingle, foreground, text)
|
||||||
a.builder.WriteString(coloredText)
|
a.builder.WriteString(coloredText)
|
||||||
return
|
return
|
||||||
|
@ -147,7 +122,7 @@ func (a *AnsiWriter) writeColoredText(background, foreground, text string) {
|
||||||
a.builder.WriteString(coloredText)
|
a.builder.WriteString(coloredText)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AnsiWriter) writeAndRemoveText(background, foreground, text, textToRemove, parentText string) string {
|
func (a *AnsiWriter) writeAndRemoveText(background, foreground AnsiColor, text, textToRemove, parentText string) string {
|
||||||
a.writeColoredText(background, foreground, text)
|
a.writeColoredText(background, foreground, text)
|
||||||
return strings.Replace(parentText, textToRemove, "", 1)
|
return strings.Replace(parentText, textToRemove, "", 1)
|
||||||
}
|
}
|
||||||
|
@ -157,60 +132,68 @@ func (a *AnsiWriter) write(background, foreground, text string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
getAnsiColors := func(background, foreground string) (string, string) {
|
bgAnsi, fgAnsi := a.asAnsiColors(background, foreground)
|
||||||
getColorString := func(color string) string {
|
|
||||||
if color == Background {
|
|
||||||
color = a.Colors.Background
|
|
||||||
} else if color == Foreground {
|
|
||||||
color = a.Colors.Foreground
|
|
||||||
} else if color == ParentBackground && a.ParentColors != nil {
|
|
||||||
color = a.ParentColors.Background
|
|
||||||
} else if color == ParentForeground && a.ParentColors != nil {
|
|
||||||
color = a.ParentColors.Foreground
|
|
||||||
} else if (color == ParentForeground || color == ParentBackground) && a.ParentColors == nil {
|
|
||||||
color = Transparent
|
|
||||||
}
|
|
||||||
return color
|
|
||||||
}
|
|
||||||
background = getColorString(background)
|
|
||||||
foreground = getColorString(foreground)
|
|
||||||
inverted := foreground == Transparent && len(background) != 0
|
|
||||||
background = a.getAnsiFromColorString(background, !inverted)
|
|
||||||
foreground = a.getAnsiFromColorString(foreground, false)
|
|
||||||
return background, foreground
|
|
||||||
}
|
|
||||||
|
|
||||||
bgAnsi, fgAnsi := getAnsiColors(background, foreground)
|
|
||||||
text = a.ansi.escapeText(text)
|
text = a.ansi.escapeText(text)
|
||||||
text = a.ansi.formatText(text)
|
text = a.ansi.formatText(text)
|
||||||
text = a.ansi.generateHyperlink(text)
|
text = a.ansi.generateHyperlink(text)
|
||||||
|
|
||||||
// first we match for any potentially valid colors enclosed in <>
|
// first we match for any potentially valid colors enclosed in <>
|
||||||
match := findAllNamedRegexMatch(colorRegex, text)
|
// i.e., find color overrides
|
||||||
for i := range match {
|
overrides := findAllNamedRegexMatch(colorRegex, text)
|
||||||
fg := match[i]["foreground"]
|
for _, override := range overrides {
|
||||||
bg := match[i]["background"]
|
fgOverride := override["foreground"]
|
||||||
if fg == Transparent && len(bg) == 0 {
|
bgOverride := override["background"]
|
||||||
bg = background
|
if fgOverride == Transparent && len(bgOverride) == 0 {
|
||||||
|
bgOverride = background
|
||||||
}
|
}
|
||||||
bg, fg = getAnsiColors(bg, fg)
|
bgOverrideAnsi, fgOverrideAnsi := a.asAnsiColors(bgOverride, fgOverride)
|
||||||
// set colors if they are empty
|
// set colors if they are empty
|
||||||
if len(bg) == 0 {
|
if bgOverrideAnsi.IsEmpty() {
|
||||||
bg = bgAnsi
|
bgOverrideAnsi = bgAnsi
|
||||||
}
|
}
|
||||||
if len(fg) == 0 {
|
if fgOverrideAnsi.IsEmpty() {
|
||||||
fg = fgAnsi
|
fgOverrideAnsi = fgAnsi
|
||||||
}
|
}
|
||||||
escapedTextSegment := match[i]["text"]
|
escapedTextSegment := override["text"]
|
||||||
innerText := match[i]["content"]
|
innerText := override["content"]
|
||||||
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
|
textBeforeColorOverride := strings.Split(text, escapedTextSegment)[0]
|
||||||
text = a.writeAndRemoveText(bgAnsi, fgAnsi, textBeforeColorOverride, textBeforeColorOverride, text)
|
text = a.writeAndRemoveText(bgAnsi, fgAnsi, textBeforeColorOverride, textBeforeColorOverride, text)
|
||||||
text = a.writeAndRemoveText(bg, fg, innerText, escapedTextSegment, text)
|
text = a.writeAndRemoveText(bgOverrideAnsi, fgOverrideAnsi, innerText, escapedTextSegment, text)
|
||||||
}
|
}
|
||||||
// color the remaining part of text with background and foreground
|
// color the remaining part of text with background and foreground
|
||||||
a.writeColoredText(bgAnsi, fgAnsi, text)
|
a.writeColoredText(bgAnsi, fgAnsi, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AnsiWriter) asAnsiColors(background, foreground string) (AnsiColor, AnsiColor) {
|
||||||
|
if backgroundValue, ok := a.isKeyword(background); ok {
|
||||||
|
background = backgroundValue
|
||||||
|
}
|
||||||
|
if foregroundValue, ok := a.isKeyword(foreground); ok {
|
||||||
|
foreground = foregroundValue
|
||||||
|
}
|
||||||
|
inverted := foreground == Transparent && len(background) != 0
|
||||||
|
backgroundAnsi := a.getAnsiFromColorString(background, !inverted)
|
||||||
|
foregroundAnsi := a.getAnsiFromColorString(foreground, false)
|
||||||
|
return backgroundAnsi, foregroundAnsi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AnsiWriter) isKeyword(color string) (string, bool) {
|
||||||
|
switch {
|
||||||
|
case color == Background:
|
||||||
|
return a.Colors.Background, true
|
||||||
|
case color == Foreground:
|
||||||
|
return a.Colors.Foreground, true
|
||||||
|
case color == ParentBackground && a.ParentColors != nil:
|
||||||
|
return a.ParentColors.Background, true
|
||||||
|
case color == ParentForeground && a.ParentColors != nil:
|
||||||
|
return a.ParentColors.Foreground, true
|
||||||
|
case (color == ParentBackground || color == ParentForeground) && a.ParentColors == nil:
|
||||||
|
return Transparent, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AnsiWriter) string() string {
|
func (a *AnsiWriter) string() string {
|
||||||
return a.builder.String()
|
return a.builder.String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,28 +6,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
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},
|
|
||||||
}
|
|
||||||
for _, tc := range cases {
|
|
||||||
renderer := &AnsiWriter{}
|
|
||||||
ansiColor := renderer.getAnsiFromColorString(tc.Color, true)
|
|
||||||
assert.Equal(t, tc.Expected, ansiColor, tc.Case)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteANSIColors(t *testing.T) {
|
func TestWriteANSIColors(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Case string
|
Case string
|
||||||
|
@ -184,6 +162,12 @@ func TestWriteANSIColors(t *testing.T) {
|
||||||
Expected: "\x1b[40m\x1b[30mtest\x1b[0m",
|
Expected: "\x1b[40m\x1b[30mtest\x1b[0m",
|
||||||
Colors: &Color{Foreground: "black", Background: "white"},
|
Colors: &Color{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[0m\x1b[47m\x1b[31mo\x1b[0m\x1b[47m\x1b[33mo\x1b[0m\x1b[47m\x1b[34mg\x1b[0m\x1b[47m\x1b[32ml\x1b[0m\x1b[47m\x1b[31me\x1b[0m",
|
||||||
|
Colors: &Color{Foreground: "black", Background: "black"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
@ -194,6 +178,7 @@ func TestWriteANSIColors(t *testing.T) {
|
||||||
ParentColors: tc.Parent,
|
ParentColors: tc.Parent,
|
||||||
Colors: tc.Colors,
|
Colors: tc.Colors,
|
||||||
terminalBackground: tc.TerminalBackground,
|
terminalBackground: tc.TerminalBackground,
|
||||||
|
ansiColors: &DefaultColors{},
|
||||||
}
|
}
|
||||||
renderer.write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
|
renderer.write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
|
||||||
got := renderer.string()
|
got := renderer.string()
|
||||||
|
|
|
@ -6,12 +6,24 @@
|
||||||
"description": "https://ohmyposh.dev/docs/config-overview",
|
"description": "https://ohmyposh.dev/docs/config-overview",
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"color": {
|
"color": {
|
||||||
|
"anyOf": [
|
||||||
|
{ "$ref": "#/definitions/color_string" },
|
||||||
|
{ "$ref": "#/definitions/palette_reference" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"color_string": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"pattern": "^(#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|black|red|green|yellow|blue|magenta|cyan|white|default|darkGray|lightRed|lightGreen|lightYellow|lightBlue|lightMagenta|lightCyan|lightWhite|transparent|parentBackground|parentForeground|background|foreground)$",
|
"pattern": "^(#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})|black|red|green|yellow|blue|magenta|cyan|white|default|darkGray|lightRed|lightGreen|lightYellow|lightBlue|lightMagenta|lightCyan|lightWhite|transparent|parentBackground|parentForeground|background|foreground)$",
|
||||||
"title": "Color string",
|
"title": "Color string",
|
||||||
"description": "https://ohmyposh.dev/docs/config-colors",
|
"description": "https://ohmyposh.dev/docs/config-colors",
|
||||||
"format": "color"
|
"format": "color"
|
||||||
},
|
},
|
||||||
|
"palette_reference": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^p:.*$",
|
||||||
|
"title": "Palette reference",
|
||||||
|
"description": "https://ohmyposh.dev/docs/config-colors#palette"
|
||||||
|
},
|
||||||
"color_templates": {
|
"color_templates": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"title": "Templates to define a color",
|
"title": "Templates to define a color",
|
||||||
|
@ -1763,6 +1775,15 @@
|
||||||
"background": { "$ref": "#/definitions/color" },
|
"background": { "$ref": "#/definitions/color" },
|
||||||
"foreground": { "$ref": "#/definitions/color" }
|
"foreground": { "$ref": "#/definitions/color" }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"palette": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Palette",
|
||||||
|
"description": "https://ohmyposh.dev/docs/config-colors#palette",
|
||||||
|
"default": {},
|
||||||
|
"patternProperties": {
|
||||||
|
".*": { "$ref": "#/definitions/color" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue