oh-my-posh/src/engine/config.go

302 lines
7.5 KiB
Go
Raw Normal View History

2022-01-26 23:38:46 -08:00
package engine
2019-03-13 04:14:30 -07:00
import (
2021-03-20 11:32:15 -07:00
"bytes"
json2 "encoding/json"
"fmt"
2022-02-03 00:20:05 -08:00
"io"
2022-01-26 04:09:21 -08:00
"oh-my-posh/color"
"oh-my-posh/environment"
2022-02-03 12:15:53 -08:00
"oh-my-posh/properties"
2019-03-13 04:14:30 -07:00
"os"
2022-01-31 04:33:36 -08:00
"path/filepath"
"strings"
2020-12-20 17:20:48 -08:00
2021-03-20 11:32:15 -07:00
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/json"
"github.com/gookit/config/v2/toml"
"github.com/gookit/config/v2/yaml"
2021-06-08 11:16:55 -07:00
"github.com/mitchellh/mapstructure"
2019-03-13 04:14:30 -07:00
)
2022-01-31 04:33:36 -08:00
const (
JSON string = "json"
YAML string = "yaml"
TOML string = "toml"
2022-02-02 03:24:31 -08:00
configVersion = 1
2022-01-31 04:33:36 -08:00
)
2021-03-20 11:32:15 -07:00
// Config holds all the theme for rendering the prompt
type Config struct {
2022-02-19 07:49:26 -08:00
Version int `json:"version"`
FinalSpace bool `json:"final_space,omitempty"`
OSC99 bool `json:"osc99,omitempty"`
ConsoleTitleTemplate string `json:"console_title_template,omitempty"`
TerminalBackground string `json:"terminal_background,omitempty"`
Blocks []*Block `json:"blocks,omitempty"`
Tooltips []*Segment `json:"tooltips,omitempty"`
TransientPrompt *ExtraPrompt `json:"transient_prompt,omitempty"`
ValidLine *ExtraPrompt `json:"valid_line,omitempty"`
ErrorLine *ExtraPrompt `json:"error_line,omitempty"`
2022-02-20 04:56:28 -08:00
SecondaryPrompt *ExtraPrompt `json:"secondary_prompt,omitempty"`
2022-03-15 12:16:04 -07:00
DebugPrompt *ExtraPrompt `json:"debug_prompt,omitempty"`
2022-02-19 07:49:26 -08:00
Palette color.Palette `json:"palette,omitempty"`
2022-01-31 04:33:36 -08:00
format string
origin string
eval bool
updated bool
2022-01-26 04:09:21 -08:00
}
// 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)
2021-06-15 12:23:08 -07:00
}
2022-02-19 07:49:26 -08:00
type ExtraPrompt struct {
2022-01-31 04:33:36 -08:00
Template string `json:"template,omitempty"`
Background string `json:"background,omitempty"`
Foreground string `json:"foreground,omitempty"`
2019-03-13 04:14:30 -07:00
}
func (cfg *Config) print(message string) {
if cfg.eval {
fmt.Printf("echo \"%s\"", message)
return
}
fmt.Println(message)
}
2022-01-31 04:33:36 -08:00
func (cfg *Config) exitWithError(err error) {
if err == nil {
return
}
defer os.Exit(1)
message := "Oh My Posh Error:\n\n" + err.Error()
if cfg.eval {
fmt.Printf("echo \"%s\"\n", message)
return
}
cfg.print(message)
2021-05-18 11:31:12 -07:00
}
2022-01-31 04:33:36 -08:00
// LoadConfig returns the default configuration including possible user overrides
func LoadConfig(env environment.Environment) *Config {
cfg := loadConfig(env)
// only migrate automatically when the switch isn't set
2022-03-12 13:04:08 -08:00
if !env.Flags().Migrate && cfg.Version != configVersion {
cfg.BackupAndMigrate(env)
2022-02-02 03:24:31 -08:00
}
2021-03-20 11:32:15 -07:00
return cfg
2019-03-13 04:14:30 -07:00
}
2022-01-31 04:33:36 -08:00
func loadConfig(env environment.Environment) *Config {
2021-03-20 11:32:15 -07:00
var cfg Config
2022-03-12 13:04:08 -08:00
configFile := env.Flags().Config
2021-03-20 11:32:15 -07:00
if configFile == "" {
2022-02-03 12:15:53 -08:00
return defaultConfig()
2019-03-13 04:14:30 -07:00
}
2021-03-20 11:32:15 -07:00
if _, err := os.Stat(configFile); os.IsNotExist(err) {
2022-01-31 04:33:36 -08:00
cfg.exitWithError(err)
}
2020-12-20 17:20:48 -08:00
2022-01-31 04:33:36 -08:00
cfg.origin = configFile
cfg.format = strings.TrimPrefix(filepath.Ext(configFile), ".")
if cfg.format == "yml" {
cfg.format = YAML
}
2022-01-31 04:33:36 -08:00
2021-03-20 11:32:15 -07:00
config.AddDriver(yaml.Driver)
config.AddDriver(json.Driver)
config.AddDriver(toml.Driver)
config.WithOptions(func(opt *config.Options) {
2021-06-08 11:16:55 -07:00
opt.DecoderConfig = &mapstructure.DecoderConfig{
2022-01-31 04:33:36 -08:00
TagName: "json",
2021-06-08 11:16:55 -07:00
}
2021-03-20 11:32:15 -07:00
})
err := config.LoadFiles(configFile)
2022-01-31 04:33:36 -08:00
cfg.exitWithError(err)
2020-12-20 17:20:48 -08:00
2021-03-20 11:32:15 -07:00
err = config.BindStruct("", &cfg)
2022-01-31 04:33:36 -08:00
cfg.exitWithError(err)
2021-03-20 11:32:15 -07:00
2021-06-15 12:23:08 -07:00
// initialize default values
if cfg.TransientPrompt == nil {
2022-02-19 07:49:26 -08:00
cfg.TransientPrompt = &ExtraPrompt{}
2021-06-15 12:23:08 -07:00
}
2022-01-31 04:33:36 -08:00
return &cfg
2021-03-20 11:32:15 -07:00
}
2022-01-31 04:33:36 -08:00
func (cfg *Config) sync() {
if !cfg.updated {
return
2021-03-20 11:32:15 -07:00
}
2022-01-31 04:33:36 -08:00
var structMap map[string]interface{}
inrec, err := json2.Marshal(cfg)
if err != nil {
return
}
err = json2.Unmarshal(inrec, &structMap)
if err != nil {
return
}
2022-01-31 04:33:36 -08:00
// remove empty structs
for k, v := range structMap {
if smap, OK := v.(map[string]interface{}); OK && len(smap) == 0 {
delete(structMap, k)
}
2021-03-20 11:32:15 -07:00
}
2022-01-31 04:33:36 -08:00
config.SetData(structMap)
}
2021-03-20 11:32:15 -07:00
2022-01-31 04:33:36 -08:00
func (cfg *Config) Export(format string) string {
cfg.sync()
2021-03-20 11:32:15 -07:00
2022-01-31 04:33:36 -08:00
if len(format) != 0 {
cfg.format = format
2021-03-20 11:32:15 -07:00
}
2022-01-31 04:33:36 -08:00
config.AddDriver(yaml.Driver)
config.AddDriver(toml.Driver)
var result bytes.Buffer
if cfg.format == JSON {
data := config.Data()
data["$schema"] = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json"
jsonEncoder := json2.NewEncoder(&result)
jsonEncoder.SetEscapeHTML(false)
jsonEncoder.SetIndent("", " ")
err := jsonEncoder.Encode(data)
cfg.exitWithError(err)
return escapeGlyphs(result.String())
2021-03-20 11:32:15 -07:00
}
2022-01-31 04:33:36 -08:00
_, err := config.DumpTo(&result, cfg.format)
cfg.exitWithError(err)
var prefix string
switch cfg.format {
case YAML:
prefix = "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n"
case TOML:
prefix = "#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n"
}
return prefix + escapeGlyphs(result.String())
2019-03-13 04:14:30 -07:00
}
func (cfg *Config) BackupAndMigrate(env environment.Environment) {
origin := cfg.backup()
cfg.Migrate(env)
2022-03-12 13:04:08 -08:00
cfg.Write()
cfg.print(fmt.Sprintf("\nOh My Posh config migrated to version %d\nBackup config available at %s\n\n", cfg.Version, origin))
}
2022-03-12 13:04:08 -08:00
func (cfg *Config) Write() {
2022-01-31 04:33:36 -08:00
content := cfg.Export(cfg.format)
2022-02-03 00:20:05 -08:00
f, err := os.OpenFile(cfg.origin, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
2022-01-31 04:33:36 -08:00
cfg.exitWithError(err)
_, err = f.WriteString(content)
cfg.exitWithError(err)
if err := f.Close(); err != nil {
cfg.exitWithError(err)
2019-03-13 04:14:30 -07:00
}
}
func (cfg *Config) backup() string {
2022-02-03 00:20:05 -08:00
dst := cfg.origin + ".bak"
source, err := os.Open(cfg.origin)
cfg.exitWithError(err)
defer source.Close()
destination, err := os.Create(dst)
cfg.exitWithError(err)
defer destination.Close()
_, err = io.Copy(destination, source)
cfg.exitWithError(err)
return dst
2022-02-03 00:20:05 -08:00
}
func escapeGlyphs(s string) string {
var builder strings.Builder
for _, r := range s {
2022-02-02 04:55:12 -08:00
// exclude regular characters and emoji
if r < 0x1000 || r > 0x10000 {
builder.WriteRune(r)
continue
}
quoted := fmt.Sprintf("\\u%04x", r)
builder.WriteString(quoted)
}
return builder.String()
}
2022-02-03 12:15:53 -08:00
func defaultConfig() *Config {
cfg := &Config{
Version: 1,
FinalSpace: true,
Blocks: []*Block{
{
Type: Prompt,
Alignment: Left,
Segments: []*Segment{
{
Type: SESSION,
Style: Diamond,
Background: "#c386f1",
Foreground: "#ffffff",
LeadingDiamond: "\uE0B6",
TrailingDiamond: "\uE0B0",
},
{
Type: PATH,
Style: Powerline,
PowerlineSymbol: "\uE0B0",
Background: "#ff479c",
Foreground: "#ffffff",
Properties: properties.Map{
properties.Style: "folder",
},
},
{
Type: SHELL,
Style: Powerline,
PowerlineSymbol: "\uE0B0",
Background: "#0077c2",
Foreground: "#ffffff",
},
{
Type: TEXT,
Style: Powerline,
PowerlineSymbol: "\uE0B0",
Background: "#ffffff",
Foreground: "#111111",
Properties: properties.Map{
properties.SegmentTemplate: " no config ",
},
},
{
Type: EXIT,
Style: Diamond,
Background: "#2e9599",
Foreground: "#ffffff",
LeadingDiamond: "<transparent,background>\uE0B0</>",
TrailingDiamond: "\uE0B4",
BackgroundTemplates: []string{
"{{ if gt .Code 0 }}#f1184c{{ end }}",
},
Properties: properties.Map{
properties.AlwaysEnabled: true,
properties.SegmentTemplate: " \uE23A",
},
},
},
},
},
}
return cfg
}