From 3efa4df08821bc59fc01147da3f8c4e60c7f4a3b Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Thu, 13 Oct 2022 20:01:51 +0200 Subject: [PATCH] feat(palette): conditional palettes --- src/cli/config_export_image.go | 2 +- src/cli/debug.go | 2 +- src/cli/print.go | 2 +- src/color/palettes.go | 6 +++ src/engine/block.go | 2 +- src/engine/config.go | 54 +++++++++++++------ src/engine/config_test.go | 77 +++++++++++++++++++++++++++ src/engine/engine_test.go | 2 +- themes/schema.json | 21 ++++++++ website/docs/configuration/colors.mdx | 46 ++++++++++++++++ 10 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 src/color/palettes.go diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go index bf588d95..6d30865e 100644 --- a/src/cli/config_export_image.go +++ b/src/cli/config_export_image.go @@ -59,7 +59,7 @@ Exports the config to an image file using customized output options.`, cfg := engine.LoadConfig(env) ansi := &color.Ansi{} ansi.InitPlain() - writerColors := cfg.MakeColors(env) + writerColors := cfg.MakeColors() writer := &color.AnsiWriter{ Ansi: ansi, TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), diff --git a/src/cli/debug.go b/src/cli/debug.go index 640219ff..36dcdc6f 100644 --- a/src/cli/debug.go +++ b/src/cli/debug.go @@ -32,7 +32,7 @@ var debugCmd = &cobra.Command{ cfg := engine.LoadConfig(env) ansi := &color.Ansi{} ansi.InitPlain() - writerColors := cfg.MakeColors(env) + writerColors := cfg.MakeColors() writer := &color.AnsiWriter{ Ansi: ansi, TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), diff --git a/src/cli/print.go b/src/cli/print.go index 7d08efde..866b0286 100644 --- a/src/cli/print.go +++ b/src/cli/print.go @@ -73,7 +73,7 @@ var printCmd = &cobra.Command{ Ansi: ansi, } } else { - writerColors := cfg.MakeColors(env) + writerColors := cfg.MakeColors() writer = &color.AnsiWriter{ Ansi: ansi, TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), diff --git a/src/color/palettes.go b/src/color/palettes.go new file mode 100644 index 00000000..a6019dc3 --- /dev/null +++ b/src/color/palettes.go @@ -0,0 +1,6 @@ +package color + +type Palettes struct { + Template string `json:"template,omitempty"` + List map[string]Palette `json:"list,omitempty"` +} diff --git a/src/engine/block.go b/src/engine/block.go index 83c37e31..b13c4bfa 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -67,7 +67,7 @@ func (b *Block) InitPlain(env environment.Environment, config *Config) { b.writer = &color.AnsiWriter{ Ansi: b.ansi, TerminalBackground: shell.ConsoleBackgroundColor(env, config.TerminalBackground), - AnsiColors: config.MakeColors(env), + AnsiColors: config.MakeColors(), } b.env = env b.executeSegmentLogic() diff --git a/src/engine/config.go b/src/engine/config.go index 1fbeb5b6..615c5e8d 100644 --- a/src/engine/config.go +++ b/src/engine/config.go @@ -9,6 +9,7 @@ import ( "oh-my-posh/environment" "oh-my-posh/properties" "oh-my-posh/segments" + "oh-my-posh/template" "os" "path/filepath" "strings" @@ -31,20 +32,21 @@ const ( // Config holds all the theme for rendering the prompt type Config struct { - Version int `json:"version"` - FinalSpace bool `json:"final_space,omitempty"` - ConsoleTitleTemplate string `json:"console_title_template,omitempty"` - TerminalBackground string `json:"terminal_background,omitempty"` - AccentColor string `json:"accent_color,omitempty"` - Blocks []*Block `json:"blocks,omitempty"` - Tooltips []*Segment `json:"tooltips,omitempty"` - TransientPrompt *Segment `json:"transient_prompt,omitempty"` - ValidLine *Segment `json:"valid_line,omitempty"` - ErrorLine *Segment `json:"error_line,omitempty"` - SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"` - DebugPrompt *Segment `json:"debug_prompt,omitempty"` - Palette color.Palette `json:"palette,omitempty"` - PWD string `json:"pwd,omitempty"` + Version int `json:"version"` + FinalSpace bool `json:"final_space,omitempty"` + ConsoleTitleTemplate string `json:"console_title_template,omitempty"` + TerminalBackground string `json:"terminal_background,omitempty"` + AccentColor string `json:"accent_color,omitempty"` + Blocks []*Block `json:"blocks,omitempty"` + Tooltips []*Segment `json:"tooltips,omitempty"` + TransientPrompt *Segment `json:"transient_prompt,omitempty"` + ValidLine *Segment `json:"valid_line,omitempty"` + ErrorLine *Segment `json:"error_line,omitempty"` + SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"` + DebugPrompt *Segment `json:"debug_prompt,omitempty"` + Palette color.Palette `json:"palette,omitempty"` + Palettes *color.Palettes `json:"palettes,omitempty"` + PWD string `json:"pwd,omitempty"` // Deprecated OSC99 bool `json:"osc99,omitempty"` @@ -55,13 +57,30 @@ type Config struct { origin string eval bool updated bool + env environment.Environment } // MakeColors creates instance of AnsiColors to use in AnsiWriter according to // environment and configuration. -func (cfg *Config) MakeColors(env environment.Environment) color.AnsiColors { - cacheDisabled := env.Getenv("OMP_CACHE_DISABLED") == "1" - return color.MakeColors(cfg.Palette, !cacheDisabled, cfg.AccentColor, env) +func (cfg *Config) MakeColors() color.AnsiColors { + cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" + return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) +} + +func (cfg *Config) getPalette() color.Palette { + if cfg.Palettes == nil { + return cfg.Palette + } + tmpl := &template.Text{ + Template: cfg.Palettes.Template, + Env: cfg.env, + } + if palette, err := tmpl.Render(); err == nil { + if p, ok := cfg.Palettes.List[palette]; ok { + return p + } + } + return cfg.Palette } func (cfg *Config) print(message string) { @@ -88,6 +107,7 @@ func (cfg *Config) exitWithError(err error) { // LoadConfig returns the default configuration including possible user overrides func LoadConfig(env environment.Environment) *Config { cfg := loadConfig(env) + cfg.env = env // only migrate automatically when the switch isn't set if !env.Flags().Migrate && cfg.Version < configVersion { cfg.BackupAndMigrate(env) diff --git a/src/engine/config_test.go b/src/engine/config_test.go index 2b67d88a..11b69345 100644 --- a/src/engine/config_test.go +++ b/src/engine/config_test.go @@ -1,6 +1,9 @@ package engine import ( + "oh-my-posh/color" + "oh-my-posh/environment" + "oh-my-posh/mock" "oh-my-posh/segments" "testing" @@ -55,3 +58,77 @@ func TestEscapeGlyphs(t *testing.T) { assert.Equal(t, tc.Expected, escapeGlyphs(tc.Input), tc.Input) } } + +func TestGetPalette(t *testing.T) { + palette := color.Palette{ + "red": "#ff0000", + "blue": "#0000ff", + } + cases := []struct { + Case string + Palettes *color.Palettes + Palette color.Palette + ExpectedPalette color.Palette + }{ + { + Case: "match", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "bash": palette, + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + ExpectedPalette: palette, + }, + { + Case: "no match, no fallback", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "fish": palette, + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + ExpectedPalette: nil, + }, + { + Case: "no match, default", + Palettes: &color.Palettes{ + Template: "{{ .Shell }}", + List: map[string]color.Palette{ + "zsh": { + "red": "#ff0001", + "blue": "#0000fb", + }, + }, + }, + Palette: palette, + ExpectedPalette: palette, + }, + { + Case: "no palettes", + ExpectedPalette: nil, + }, + } + for _, tc := range cases { + env := &mock.MockedEnvironment{} + env.On("TemplateCache").Return(&environment.TemplateCache{ + Env: map[string]string{}, + Shell: "bash", + }) + cfg := &Config{ + env: env, + Palette: tc.Palette, + Palettes: tc.Palettes, + } + got := cfg.getPalette() + assert.Equal(t, tc.ExpectedPalette, got, tc.Case) + } +} diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go index 782c8b6b..e5dabec0 100644 --- a/src/engine/engine_test.go +++ b/src/engine/engine_test.go @@ -100,7 +100,7 @@ func engineRender() { ansi := &color.Ansi{} ansi.InitPlain() - writerColors := cfg.MakeColors(env) + writerColors := cfg.MakeColors() writer := &color.AnsiWriter{ Ansi: ansi, TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), diff --git a/themes/schema.json b/themes/schema.json index 32b96691..a0847a9e 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -2900,6 +2900,27 @@ } } }, + "palettes": { + "type": "object", + "title": "Palettes", + "description": "https://ohmyposh.dev/docs/configuration/colors#palettes", + "default": {}, + "properties": { + "template": { + "type": "string", + "title": "Prompt Template" + }, + "list": { + "type": "object", + "title": "List of palettes", + "patternProperties": { + ".*": { + "$ref": "#/definitions/palette" + } + } + } + } + }, "accent_color": { "title": "Accent color", "$ref": "#/definitions/color" diff --git a/website/docs/configuration/colors.mdx b/website/docs/configuration/colors.mdx index a7cffd97..5b1f89a8 100644 --- a/website/docs/configuration/colors.mdx +++ b/website/docs/configuration/colors.mdx @@ -17,6 +17,7 @@ Oh My Posh supports multiple different color references, being: as well as 8 extended ANSI colors: `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` + - 256 color palette using their number representation. For example `0` is black, `1` is red, `2` is green, etc. - The `transparent` keyword which can be used to create either a transparent foreground override @@ -190,6 +191,51 @@ For example, `p:foreground` and `p:background` will be correctly set to "#CAF0F8 } ``` +## Palettes + +If you want to use a `palette` conditionally, for example for **light or dark mode**, you can define multiple +`palettes` and a [template][templates] that resolves to the `palette` key. The `template` is evaluated at +runtime so your prompt can change at any time based on the outcome of the `template`. + +Take the following configuration: + +```json +{ + "$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json", + "palettes": { + "template": "{{ if eq .Shell \"pwsh\" }}latte{{ else }}frappe{{ end }}", + "list": { + "latte": { + "black": "#262B44", + "green": "#59C9A5", + "orange": "#F07623", + "red": "#e64553", + "white": "#E0DEF4", + "yellow": "#df8e1d", + "blue": "#7287fd" + }, + "frappe": { + "black": "#262B44", + "green": "#59C9A5", + "orange": "#F07623", + "red": "#D81E5B", + "white": "#E0DEF4", + "yellow": "#F3AE35", + "blue": "#4B95E9" + } + } + }, + "blocks": [ + ... + ] +} +``` + +In this case, when the shell is `pwsh`, the `latte` palette will be used, otherwise it uses the `frappe` palette. If you want, +you could also add `frappe` as the default `palette`, given that one is used as a fallback when not match can be found based on what +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. + [hexcolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ [ansicolors]: https://htmlcolorcodes.com/color-chart/material-design-color-chart/ [git]: /docs/segments/git