From 3c6abf6163b2eca09f9b34bd4ccb141a63bd1e74 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Mon, 1 Jul 2024 17:01:05 +0200 Subject: [PATCH] refactor(config): extract to module --- src/cli/config_export.go | 12 +- src/cli/config_export_image.go | 5 +- src/cli/config_migrate.go | 6 +- src/cli/config_migrate_glyphs.go | 6 +- src/cli/debug.go | 5 +- src/cli/init.go | 9 +- src/cli/print.go | 2 +- src/cli/root.go | 4 +- src/cli/upgrade.go | 4 +- src/config/backup.go | 107 ++++ src/config/block.go | 119 ++++ src/{engine => config}/block_test.go | 2 +- src/config/config.go | 75 +++ src/{engine => config}/config_test.go | 21 +- src/config/default.go | 199 +++++++ src/config/load.go | 95 ++++ src/{engine => config}/migrate.go | 8 +- src/config/migrate_glyphs.go | 140 +++++ src/config/migrate_glyphs_test.go | 35 ++ src/{engine => config}/migrate_test.go | 2 +- src/{engine => config}/responsive.go | 2 +- src/{engine => config}/responsive_test.go | 2 +- src/config/segment.go | 260 +++++++++ src/{engine => config}/segment_test.go | 10 +- .../segment.go => config/segment_types.go} | 271 +-------- src/engine/block.go | 328 ----------- src/engine/config.go | 525 ------------------ src/engine/debug.go | 30 +- src/engine/engine.go | 242 +++++++- src/engine/engine_test.go | 9 +- src/engine/migrate_glyphs.go | 61 -- src/engine/migrate_glyphs_test.go | 16 - src/engine/new.go | 3 +- src/engine/prompt.go | 13 +- src/engine/tooltip.go | 29 +- 35 files changed, 1355 insertions(+), 1302 deletions(-) create mode 100644 src/config/backup.go create mode 100644 src/config/block.go rename src/{engine => config}/block_test.go (98%) create mode 100644 src/config/config.go rename src/{engine => config}/config_test.go (76%) create mode 100644 src/config/default.go create mode 100644 src/config/load.go rename src/{engine => config}/migrate.go (98%) create mode 100644 src/config/migrate_glyphs.go create mode 100644 src/config/migrate_glyphs_test.go rename src/{engine => config}/migrate_test.go (99%) rename src/{engine => config}/responsive.go (96%) rename src/{engine => config}/responsive_test.go (98%) create mode 100644 src/config/segment.go rename src/{engine => config}/segment_test.go (96%) rename src/{engine/segment.go => config/segment_types.go} (65%) delete mode 100644 src/engine/block.go delete mode 100644 src/engine/config.go delete mode 100644 src/engine/migrate_glyphs.go delete mode 100644 src/engine/migrate_glyphs_test.go diff --git a/src/cli/config_export.go b/src/cli/config_export.go index ab769f96..95624b8b 100644 --- a/src/cli/config_export.go +++ b/src/cli/config_export.go @@ -7,7 +7,7 @@ import ( "slices" "strings" - "github.com/jandedobbeleer/oh-my-posh/src/engine" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/spf13/cobra" @@ -38,12 +38,12 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std Run: func(_ *cobra.Command, _ []string) { env := &platform.Shell{ CmdFlags: &platform.Flags{ - Config: config, + Config: configFlag, }, } env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) if len(output) == 0 && len(format) == 0 { // usage error @@ -69,11 +69,11 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std switch format { case "json", "jsonc": - format = engine.JSON + format = config.JSON case "toml", "tml": - format = engine.TOML + format = config.TOML case "yaml", "yml": - format = engine.YAML + format = config.YAML default: // data error os.Exit(65) diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go index e779ce38..93013301 100644 --- a/src/cli/config_export_image.go +++ b/src/cli/config_export_image.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/engine" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/shell" @@ -49,7 +50,7 @@ Exports the config to an image file using customized output options.`, Run: func(_ *cobra.Command, _ []string) { env := &platform.Shell{ CmdFlags: &platform.Flags{ - Config: config, + Config: configFlag, Shell: shell.GENERIC, TerminalWidth: 150, }, @@ -57,7 +58,7 @@ Exports the config to an image file using customized output options.`, env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) // set sane defaults for things we don't print cfg.ConsoleTitleTemplate = "" diff --git a/src/cli/config_migrate.go b/src/cli/config_migrate.go index a01db01b..b7560002 100644 --- a/src/cli/config_migrate.go +++ b/src/cli/config_migrate.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/jandedobbeleer/oh-my-posh/src/engine" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/spf13/cobra" @@ -41,13 +41,13 @@ A backup of the current config can be found at ~/myconfig.omp.json.bak.`, Run: func(_ *cobra.Command, _ []string) { env := &platform.Shell{ CmdFlags: &platform.Flags{ - Config: config, + Config: configFlag, Migrate: true, }, } env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) if write { cfg.BackupAndMigrate() return diff --git a/src/cli/config_migrate_glyphs.go b/src/cli/config_migrate_glyphs.go index c43cbf70..41134d9d 100644 --- a/src/cli/config_migrate_glyphs.go +++ b/src/cli/config_migrate_glyphs.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/jandedobbeleer/oh-my-posh/src/engine" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/spf13/cobra" @@ -36,13 +36,13 @@ A backup of the current config can be found at ~/myconfig.omp.json.bak.`, Run: func(_ *cobra.Command, _ []string) { env := &platform.Shell{ CmdFlags: &platform.Flags{ - Config: config, + Config: configFlag, }, } env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) cfg.MigrateGlyphs = true if len(format) == 0 { diff --git a/src/cli/debug.go b/src/cli/debug.go index a9818159..9e8c714b 100644 --- a/src/cli/debug.go +++ b/src/cli/debug.go @@ -5,6 +5,7 @@ import ( "time" "github.com/jandedobbeleer/oh-my-posh/src/build" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/engine" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/shell" @@ -24,7 +25,7 @@ var debugCmd = &cobra.Command{ env := &platform.Shell{ CmdFlags: &platform.Flags{ - Config: config, + Config: configFlag, Debug: true, PWD: pwd, Shell: shellName, @@ -35,7 +36,7 @@ var debugCmd = &cobra.Command{ env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) // add variables to the environment env.Var = cfg.Var diff --git a/src/cli/init.go b/src/cli/init.go index 4f82296e..17d40080 100644 --- a/src/cli/init.go +++ b/src/cli/init.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/jandedobbeleer/oh-my-posh/src/engine" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/terminal" @@ -58,7 +58,7 @@ func runInit(shellName string) { env := &platform.Shell{ CmdFlags: &platform.Flags{ Shell: shellName, - Config: config, + Config: configFlag, Strict: strict, Manual: manual, }, @@ -66,7 +66,7 @@ func runInit(shellName string) { env.Init() defer env.Close() - cfg := engine.LoadConfig(env) + cfg := config.Load(env) shell.Transient = cfg.TransientPrompt != nil shell.ErrorLine = cfg.ErrorLine != nil || cfg.ValidLine != nil @@ -79,7 +79,8 @@ func runInit(shellName string) { if !cfg.DisableCursorPositioning && (i == 0 && block.Newline) { shell.CursorPositioning = true } - if block.Type == engine.RPrompt { + + if block.Type == config.RPrompt { shell.RPrompt = true } } diff --git a/src/cli/print.go b/src/cli/print.go index 92df12a8..79807f3e 100644 --- a/src/cli/print.go +++ b/src/cli/print.go @@ -50,7 +50,7 @@ var printCmd = &cobra.Command{ } flags := &platform.Flags{ - Config: config, + Config: configFlag, PWD: pwd, PSWD: pswd, ErrorCode: status, diff --git a/src/cli/root.go b/src/cli/root.go index 54bb93ae..7eb48899 100644 --- a/src/cli/root.go +++ b/src/cli/root.go @@ -10,7 +10,7 @@ import ( ) var ( - config string + configFlag string displayVersion bool ) @@ -48,7 +48,7 @@ var ( ) func init() { - RootCmd.PersistentFlags().StringVarP(&config, "config", "c", "", "config file path") + RootCmd.PersistentFlags().StringVarP(&configFlag, "config", "c", "", "config file path") RootCmd.Flags().BoolVarP(&initialize, "init", "i", false, "init (deprecated)") RootCmd.Flags().BoolVar(&displayVersion, "version", false, "version") RootCmd.Flags().StringVarP(&shellName, "shell", "s", "", "shell (deprecated)") diff --git a/src/cli/upgrade.go b/src/cli/upgrade.go index 0f420591..a3f65108 100644 --- a/src/cli/upgrade.go +++ b/src/cli/upgrade.go @@ -5,7 +5,7 @@ import ( "runtime" "slices" - "github.com/jandedobbeleer/oh-my-posh/src/engine" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/terminal" "github.com/jandedobbeleer/oh-my-posh/src/upgrade" @@ -48,7 +48,7 @@ var upgradeCmd = &cobra.Command{ return } - cfg := engine.LoadConfig(env) + cfg := config.Load(env) if _, hasNotice := upgrade.Notice(env, true); !hasNotice { if !cfg.DisableNotice { diff --git a/src/config/backup.go b/src/config/backup.go new file mode 100644 index 00000000..271f51f4 --- /dev/null +++ b/src/config/backup.go @@ -0,0 +1,107 @@ +package config + +import ( + "bytes" + "io" + "os" + "strings" + + json "github.com/goccy/go-json" + yaml "github.com/goccy/go-yaml" + toml "github.com/pelletier/go-toml/v2" +) + +func (cfg *Config) Backup() { + dst := cfg.origin + ".bak" + source, err := os.Open(cfg.origin) + if err != nil { + return + } + defer source.Close() + destination, err := os.Create(dst) + if err != nil { + return + } + defer destination.Close() + _, err = io.Copy(destination, source) + if err != nil { + return + } +} + +func (cfg *Config) Export(format string) string { + if len(format) != 0 { + cfg.Format = format + } + + var result bytes.Buffer + + switch cfg.Format { + case YAML: + prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" + yamlEncoder := yaml.NewEncoder(&result) + + err := yamlEncoder.Encode(cfg) + if err != nil { + return "" + } + + return prefix + result.String() + case JSON: + jsonEncoder := json.NewEncoder(&result) + jsonEncoder.SetEscapeHTML(false) + jsonEncoder.SetIndent("", " ") + _ = jsonEncoder.Encode(cfg) + prefix := "{\n \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\"," + data := strings.Replace(result.String(), "{", prefix, 1) + return escapeGlyphs(data, cfg.MigrateGlyphs) + case TOML: + prefix := "#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" + tomlEncoder := toml.NewEncoder(&result) + tomlEncoder.SetIndentTables(true) + + err := tomlEncoder.Encode(cfg) + if err != nil { + return "" + } + + return prefix + result.String() + } + + // unsupported format + return "" +} + +func (cfg *Config) BackupAndMigrate() { + cfg.Backup() + cfg.Migrate() + cfg.Write(cfg.Format) +} + +func (cfg *Config) Write(format string) { + content := cfg.Export(format) + if len(content) == 0 { + // we are unable to perform the export + os.Exit(65) + return + } + + destination := cfg.Output + if len(destination) == 0 { + destination = cfg.origin + } + + f, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return + } + + defer func() { + _ = f.Close() + }() + + _, err = f.WriteString(content) + if err != nil { + return + } +} diff --git a/src/config/block.go b/src/config/block.go new file mode 100644 index 00000000..36f01b78 --- /dev/null +++ b/src/config/block.go @@ -0,0 +1,119 @@ +package config + +import ( + "sync" + + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +// BlockType type of block +type BlockType string + +// BlockAlignment aligment of a Block +type BlockAlignment string + +// Overflow defines how to handle a right block that overflows with the previous block +type Overflow string + +const ( + // Prompt writes one or more Segments + Prompt BlockType = "prompt" + // LineBreak creates a line break in the prompt + LineBreak BlockType = "newline" + // RPrompt is a right aligned prompt + RPrompt BlockType = "rprompt" + // Left aligns left + Left BlockAlignment = "left" + // Right aligns right + Right BlockAlignment = "right" + // Break adds a line break + Break Overflow = "break" + // Hide hides the block + Hide Overflow = "hide" +) + +// Block defines a part of the prompt with optional segments +type Block struct { + Type BlockType `json:"type,omitempty" toml:"type,omitempty"` + Alignment BlockAlignment `json:"alignment,omitempty" toml:"alignment,omitempty"` + Segments []*Segment `json:"segments,omitempty" toml:"segments,omitempty"` + Newline bool `json:"newline,omitempty" toml:"newline,omitempty"` + Filler string `json:"filler,omitempty" toml:"filler,omitempty"` + Overflow Overflow `json:"overflow,omitempty" toml:"overflow,omitempty"` + + LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"` + TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"` + + // Deprecated: keep the logic for legacy purposes + HorizontalOffset int `json:"horizontal_offset,omitempty" toml:"horizontal_offset,omitempty"` + VerticalOffset int `json:"vertical_offset,omitempty" toml:"vertical_offset,omitempty"` + + MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"` + MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` + + env platform.Environment +} + +func (b *Block) Init(env platform.Environment) { + b.env = env + b.executeSegmentLogic() +} + +func (b *Block) InitPlain(env platform.Environment, config *Config) { + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, config.TerminalBackground) + terminal.AnsiColors = config.MakeColors() + + b.env = env + b.executeSegmentLogic() +} + +func (b *Block) Enabled() bool { + if b.Type == LineBreak { + return true + } + + for _, segment := range b.Segments { + if segment.Enabled { + return true + } + } + + return false +} + +func (b *Block) executeSegmentLogic() { + if shouldHideForWidth(b.env, b.MinWidth, b.MaxWidth) { + return + } + + b.setEnabledSegments() + b.setSegmentsText() +} + +func (b *Block) setEnabledSegments() { + wg := sync.WaitGroup{} + wg.Add(len(b.Segments)) + defer wg.Wait() + + for _, segment := range b.Segments { + go func(s *Segment) { + defer wg.Done() + s.SetEnabled(b.env) + }(segment) + } +} + +func (b *Block) setSegmentsText() { + wg := sync.WaitGroup{} + wg.Add(len(b.Segments)) + defer wg.Wait() + for _, segment := range b.Segments { + go func(s *Segment) { + defer wg.Done() + s.setText() + }(segment) + } +} diff --git a/src/engine/block_test.go b/src/config/block_test.go similarity index 98% rename from src/engine/block_test.go rename to src/config/block_test.go index b108e895..5a19011d 100644 --- a/src/engine/block_test.go +++ b/src/config/block_test.go @@ -1,4 +1,4 @@ -package engine +package config import ( "testing" diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 00000000..faacff35 --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,75 @@ +package config + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +const ( + JSON string = "json" + YAML string = "yaml" + TOML string = "toml" + + Version = 2 +) + +// Config holds all the theme for rendering the prompt +type Config struct { + Version int `json:"version" toml:"version"` + FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"` + ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"` + TerminalBackground string `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"` + AccentColor string `json:"accent_color,omitempty" toml:"accent_color,omitempty"` + Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty"` + Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty"` + TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty"` + ValidLine *Segment `json:"valid_line,omitempty" toml:"valid_line,omitempty"` + ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty"` + SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty"` + DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"` + Palette terminal.Palette `json:"palette,omitempty" toml:"palette,omitempty"` + Palettes *terminal.Palettes `json:"palettes,omitempty" toml:"palettes,omitempty"` + Cycle terminal.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty"` + ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty"` + PWD string `json:"pwd,omitempty" toml:"pwd,omitempty"` + Var map[string]any `json:"var,omitempty" toml:"var,omitempty"` + DisableCursorPositioning bool `json:"disable_cursor_positioning,omitempty" toml:"disable_cursor_positioning,omitempty"` + PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty"` + DisableNotice bool `json:"disable_notice,omitempty" toml:"disable_notice,omitempty"` + AutoUpgrade bool `json:"auto_upgrade,omitempty" toml:"auto_upgrade,omitempty"` + ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty"` + + // Deprecated + OSC99 bool `json:"osc99,omitempty" toml:"osc99,omitempty"` + + Output string `json:"-" toml:"-"` + MigrateGlyphs bool `json:"-" toml:"-"` + Format string `json:"-" toml:"-"` + + origin string + // eval bool + updated bool + env platform.Environment +} + +func (cfg *Config) MakeColors() terminal.ColorString { + cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" + return terminal.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) +} + +func (cfg *Config) getPalette() terminal.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 +} diff --git a/src/engine/config_test.go b/src/config/config_test.go similarity index 76% rename from src/engine/config_test.go rename to src/config/config_test.go index 5e765137..212d8afc 100644 --- a/src/engine/config_test.go +++ b/src/config/config_test.go @@ -1,4 +1,4 @@ -package engine +package config import ( "testing" @@ -11,25 +11,6 @@ import ( mock2 "github.com/stretchr/testify/mock" ) -func TestEscapeGlyphs(t *testing.T) { - cases := []struct { - Input string - Expected string - }{ - {Input: "󰉋", Expected: "\\udb80\\ude4b"}, - {Input: "a", Expected: "a"}, - {Input: "\ue0b4", Expected: "\\ue0b4"}, - {Input: "\ufd03", Expected: "\\ufd03"}, - {Input: "}", Expected: "}"}, - {Input: "🏚", Expected: "🏚"}, - {Input: "\U000F011B", Expected: "\\udb80\\udd1b"}, - {Input: "󰄛", Expected: "\\udb80\\udd1b"}, - } - for _, tc := range cases { - assert.Equal(t, tc.Expected, escapeGlyphs(tc.Input, false), tc.Input) - } -} - func TestGetPalette(t *testing.T) { palette := terminal.Palette{ "red": "#ff0000", diff --git a/src/config/default.go b/src/config/default.go new file mode 100644 index 00000000..087b55a0 --- /dev/null +++ b/src/config/default.go @@ -0,0 +1,199 @@ +package config + +import ( + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/properties" + "github.com/jandedobbeleer/oh-my-posh/src/segments" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" +) + +func Default(env platform.Environment, warning bool) *Config { + exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}" + exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} " + if warning { + exitBackgroundTemplate = "p:red" + exitTemplate = " CONFIG ERROR " + } + cfg := &Config{ + Version: 2, + FinalSpace: true, + Blocks: []*Block{ + { + Type: Prompt, + Alignment: Left, + Segments: []*Segment{ + { + Type: SESSION, + Style: Diamond, + LeadingDiamond: "\ue0b6", + TrailingDiamond: "\ue0b0", + Background: "p:yellow", + Foreground: "p:black", + Template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} ", + }, + { + Type: PATH, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Background: "p:orange", + Foreground: "p:white", + Properties: properties.Map{ + properties.Style: "folder", + }, + Template: " \uea83 {{ path .Path .Location }} ", + }, + { + Type: GIT, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Background: "p:green", + BackgroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}", + "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}", + "{{ if gt .Ahead 0 }}#49416D{{ end }}", + "{{ if gt .Behind 0 }}#7A306C{{ end }}", + }, + Foreground: "p:black", + ForegroundTemplates: []string{ + "{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}", + "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}", + "{{ if gt .Ahead 0 }}p:white{{ end }}", + }, + Properties: properties.Map{ + segments.BranchMaxLength: 25, + segments.FetchStatus: true, + segments.FetchUpstreamIcon: true, + }, + Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll + }, + { + Type: ROOT, + Style: Powerline, + PowerlineSymbol: "\ue0b0", + Background: "p:yellow", + Foreground: "p:white", + Template: " \uf0e7 ", + }, + { + Type: STATUS, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Background: "p:blue", + BackgroundTemplates: []string{ + exitBackgroundTemplate, + }, + Foreground: "p:white", + Properties: properties.Map{ + properties.AlwaysEnabled: true, + }, + Template: exitTemplate, + }, + }, + }, + { + Type: RPrompt, + Segments: []*Segment{ + { + Type: NODE, + Style: Plain, + Background: "transparent", + Foreground: "p:green", + Template: "\ue718 ", + Properties: properties.Map{ + segments.HomeEnabled: false, + segments.FetchPackageManager: false, + segments.DisplayMode: "files", + }, + }, + { + Type: GOLANG, + Style: Plain, + Background: "transparent", + Foreground: "p:blue", + Template: "\ue626 ", + Properties: properties.Map{ + properties.FetchVersion: false, + }, + }, + { + Type: PYTHON, + Style: Plain, + Background: "transparent", + Foreground: "p:yellow", + Template: "\ue235 ", + Properties: properties.Map{ + properties.FetchVersion: false, + segments.DisplayMode: "files", + segments.FetchVirtualEnv: false, + }, + }, + { + Type: SHELL, + Style: Plain, + Background: "transparent", + Foreground: "p:white", + Template: "in {{ .Name }} ", + }, + { + Type: TIME, + Style: Plain, + Background: "transparent", + Foreground: "p:white", + Template: "at {{ .CurrentDate | date \"15:04:05\" }}", + }, + }, + }, + }, + ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}", + Palette: terminal.Palette{ + "black": "#262B44", + "blue": "#4B95E9", + "green": "#59C9A5", + "orange": "#F07623", + "red": "#D81E5B", + "white": "#E0DEF4", + "yellow": "#F3AE35", + }, + SecondaryPrompt: &Segment{ + Background: "transparent", + Foreground: "p:black", + Template: "\ue0b6<,p:yellow> > \ue0b0 ", + }, + TransientPrompt: &Segment{ + Background: "transparent", + Foreground: "p:black", + Template: "\ue0b6<,p:yellow> {{ .Folder }} \ue0b0 ", + }, + Tooltips: []*Segment{ + { + Type: AWS, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Background: "p:orange", + Foreground: "p:white", + Template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ", + Properties: properties.Map{ + properties.DisplayDefault: true, + }, + Tips: []string{"aws"}, + }, + { + Type: AZ, + Style: Diamond, + LeadingDiamond: "\ue0b0", + TrailingDiamond: "\ue0b4", + Background: "p:blue", + Foreground: "p:white", + Template: " \uebd8 {{ .Name }} ", + Properties: properties.Map{ + properties.DisplayDefault: true, + }, + Tips: []string{"az"}, + }, + }, + } + cfg.env = env + return cfg +} diff --git a/src/config/load.go b/src/config/load.go new file mode 100644 index 00000000..79a434ca --- /dev/null +++ b/src/config/load.go @@ -0,0 +1,95 @@ +package config + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/gookit/goutil/jsonutil" + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/shell" + + json "github.com/goccy/go-json" + yaml "github.com/goccy/go-yaml" + toml "github.com/pelletier/go-toml/v2" +) + +// LoadConfig returns the default configuration including possible user overrides +func Load(env platform.Environment) *Config { + cfg := loadConfig(env) + + // only migrate automatically when the switch isn't set + if !env.Flags().Migrate && cfg.Version < Version { + cfg.BackupAndMigrate() + } + + if !cfg.ShellIntegration { + return cfg + } + + // bash - ok + // fish - ok + // pwsh - ok + // zsh - ok + // cmd - ok, as of v1.4.25 (chrisant996/clink#457, fixed in chrisant996/clink@8a5d7ea) + // nu - built-in (and bugged) feature - nushell/nushell#5585, https://www.nushell.sh/blog/2022-08-16-nushell-0_67.html#shell-integration-fdncred-and-tyriar + // elv - broken OSC sequences + // xonsh - broken OSC sequences + // tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future + switch env.Shell() { + case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU: + cfg.ShellIntegration = false + } + + return cfg +} + +func loadConfig(env platform.Environment) *Config { + defer env.Trace(time.Now()) + configFile := env.Flags().Config + + if len(configFile) == 0 { + env.Debug("no config file specified, using default") + return Default(env, false) + } + + var cfg Config + cfg.origin = configFile + cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".") + cfg.env = env + + data, err := os.ReadFile(configFile) + if err != nil { + env.DebugF("error reading config file: %s", err) + return Default(env, true) + } + + switch cfg.Format { + case "yml", "yaml": + cfg.Format = YAML + err = yaml.Unmarshal(data, &cfg) + case "jsonc", "json": + cfg.Format = JSON + + str := jsonutil.StripComments(string(data)) + data = []byte(str) + + decoder := json.NewDecoder(bytes.NewReader(data)) + err = decoder.Decode(&cfg) + case "toml", "tml": + cfg.Format = TOML + err = toml.Unmarshal(data, &cfg) + default: + err = fmt.Errorf("unsupported config file format: %s", cfg.Format) + } + + if err != nil { + env.DebugF("error decoding config file: %s", err) + return Default(env, true) + } + + return &cfg +} diff --git a/src/engine/migrate.go b/src/config/migrate.go similarity index 98% rename from src/engine/migrate.go rename to src/config/migrate.go index d1c9b805..56a88ded 100644 --- a/src/engine/migrate.go +++ b/src/config/migrate.go @@ -1,4 +1,4 @@ -package engine +package config import ( "fmt" @@ -30,7 +30,7 @@ func (cfg *Config) Migrate() { cfg.ConsoleTitleTemplate = strings.ReplaceAll(cfg.ConsoleTitleTemplate, ".Path", ".PWD") } cfg.updated = true - cfg.Version = configVersion + cfg.Version = Version } func (segment *Segment) migrate(env platform.Environment, version int) { @@ -43,7 +43,7 @@ func (segment *Segment) migrate(env platform.Environment, version int) { } func (segment *Segment) migrationOne(env platform.Environment) { - if err := segment.mapSegmentWithWriter(env); err != nil { + if err := segment.MapSegmentWithWriter(env); err != nil { return } // General properties that need replacement @@ -163,7 +163,7 @@ func (segment *Segment) migrationOne(env platform.Environment) { } func (segment *Segment) migrationTwo(env platform.Environment) { - if err := segment.mapSegmentWithWriter(env); err != nil { + if err := segment.MapSegmentWithWriter(env); err != nil { return } if !segment.hasProperty(segmentTemplate) { diff --git a/src/config/migrate_glyphs.go b/src/config/migrate_glyphs.go new file mode 100644 index 00000000..8ca01e4d --- /dev/null +++ b/src/config/migrate_glyphs.go @@ -0,0 +1,140 @@ +package config + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/platform/net" +) + +type ConnectionError struct { + reason string +} + +func (f *ConnectionError) Error() string { + return f.reason +} + +type codePoints map[uint64]uint64 + +func getGlyphCodePoints() (codePoints, error) { + var codePoints = make(codePoints) + + ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) + defer cncl() + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://ohmyposh.dev/codepoints.csv", nil) + if err != nil { + return codePoints, &ConnectionError{reason: err.Error()} + } + + response, err := net.HTTPClient.Do(request) + if err != nil { + return codePoints, err + } + + defer response.Body.Close() + + bytes, err := io.ReadAll(response.Body) + if err != nil { + return codePoints, err + } + + lines := strings.Split(string(bytes), "\n") + + for _, line := range lines { + fields := strings.Split(line, ",") + if len(fields) < 2 { + continue + } + + oldGlyph, err := strconv.ParseUint(fields[0], 16, 32) + if err != nil { + continue + } + + newGlyph, err := strconv.ParseUint(fields[1], 16, 32) + if err != nil { + continue + } + + codePoints[oldGlyph] = newGlyph + } + + return codePoints, nil +} + +func escapeGlyphs(s string, migrate bool) string { + shouldExclude := func(r rune) bool { + if r < 0x1000 { // Basic Multilingual Plane + return true + } + if r > 0x1F600 && r < 0x1F64F { // Emoticons + return true + } + if r > 0x1F300 && r < 0x1F5FF { // Misc Symbols and Pictographs + return true + } + if r > 0x1F680 && r < 0x1F6FF { // Transport and Map + return true + } + if r > 0x2600 && r < 0x26FF { // Misc symbols + return true + } + if r > 0x2700 && r < 0x27BF { // Dingbats + return true + } + if r > 0xFE00 && r < 0xFE0F { // Variation Selectors + return true + } + if r > 0x1F900 && r < 0x1F9FF { // Supplemental Symbols and Pictographs + return true + } + if r > 0x1F1E6 && r < 0x1F1FF { // Flags + return true + } + return false + } + + var cp codePoints + var err error + if migrate { + cp, err = getGlyphCodePoints() + if err != nil { + migrate = false + } + } + + var builder strings.Builder + for _, r := range s { + // exclude regular characters and emojis + if shouldExclude(r) { + builder.WriteRune(r) + continue + } + + if migrate { + if val, OK := cp[uint64(r)]; OK { + r = rune(val) + } + } + + if r > 0x10000 { + // calculate surrogate pairs + one := 0xd800 + (((r - 0x10000) >> 10) & 0x3ff) + two := 0xdc00 + ((r - 0x10000) & 0x3ff) + quoted := fmt.Sprintf("\\u%04x\\u%04x", one, two) + builder.WriteString(quoted) + continue + } + + quoted := fmt.Sprintf("\\u%04x", r) + builder.WriteString(quoted) + } + return builder.String() +} diff --git a/src/config/migrate_glyphs_test.go b/src/config/migrate_glyphs_test.go new file mode 100644 index 00000000..071c2173 --- /dev/null +++ b/src/config/migrate_glyphs_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetCodePoints(t *testing.T) { + codepoints, err := getGlyphCodePoints() + if connectionError, ok := err.(*ConnectionError); ok { + t.Log(connectionError.Error()) + return + } + assert.Equal(t, 1939, len(codepoints)) +} + +func TestEscapeGlyphs(t *testing.T) { + cases := []struct { + Input string + Expected string + }{ + {Input: "󰉋", Expected: "\\udb80\\ude4b"}, + {Input: "a", Expected: "a"}, + {Input: "\ue0b4", Expected: "\\ue0b4"}, + {Input: "\ufd03", Expected: "\\ufd03"}, + {Input: "}", Expected: "}"}, + {Input: "🏚", Expected: "🏚"}, + {Input: "\U000F011B", Expected: "\\udb80\\udd1b"}, + {Input: "󰄛", Expected: "\\udb80\\udd1b"}, + } + for _, tc := range cases { + assert.Equal(t, tc.Expected, escapeGlyphs(tc.Input, false), tc.Input) + } +} diff --git a/src/engine/migrate_test.go b/src/config/migrate_test.go similarity index 99% rename from src/engine/migrate_test.go rename to src/config/migrate_test.go index b98471c1..a2721aa0 100644 --- a/src/engine/migrate_test.go +++ b/src/config/migrate_test.go @@ -1,4 +1,4 @@ -package engine +package config import ( "testing" diff --git a/src/engine/responsive.go b/src/config/responsive.go similarity index 96% rename from src/engine/responsive.go rename to src/config/responsive.go index 777ede4d..a4bc06f2 100644 --- a/src/engine/responsive.go +++ b/src/config/responsive.go @@ -1,4 +1,4 @@ -package engine +package config import "github.com/jandedobbeleer/oh-my-posh/src/platform" diff --git a/src/engine/responsive_test.go b/src/config/responsive_test.go similarity index 98% rename from src/engine/responsive_test.go rename to src/config/responsive_test.go index 4d7c54d6..9aeb102d 100644 --- a/src/engine/responsive_test.go +++ b/src/config/responsive_test.go @@ -1,4 +1,4 @@ -package engine +package config import ( "testing" diff --git a/src/config/segment.go b/src/config/segment.go new file mode 100644 index 00000000..f034da12 --- /dev/null +++ b/src/config/segment.go @@ -0,0 +1,260 @@ +package config + +import ( + "fmt" + "runtime/debug" + "strings" + "time" + + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/properties" + "github.com/jandedobbeleer/oh-my-posh/src/template" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" + + c "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// SegmentStyle the style of segment, for more information, see the constants +type SegmentStyle string + +func (s *SegmentStyle) resolve(env platform.Environment, context any) SegmentStyle { + txtTemplate := &template.Text{ + Context: context, + Env: env, + } + txtTemplate.Template = string(*s) + value, err := txtTemplate.Render() + // default to Plain + if err != nil || len(value) == 0 { + return Plain + } + return SegmentStyle(value) +} + +type Segment struct { + Type SegmentType `json:"type,omitempty" toml:"type,omitempty"` + Tips []string `json:"tips,omitempty" toml:"tips,omitempty"` + Style SegmentStyle `json:"style,omitempty" toml:"style,omitempty"` + PowerlineSymbol string `json:"powerline_symbol,omitempty" toml:"powerline_symbol,omitempty"` + LeadingPowerlineSymbol string `json:"leading_powerline_symbol,omitempty" toml:"leading_powerline_symbol,omitempty"` + InvertPowerline bool `json:"invert_powerline,omitempty" toml:"invert_powerline,omitempty"` + Foreground string `json:"foreground,omitempty" toml:"foreground,omitempty"` + ForegroundTemplates template.List `json:"foreground_templates,omitempty" toml:"foreground_templates,omitempty"` + Background string `json:"background,omitempty" toml:"background,omitempty"` + BackgroundTemplates template.List `json:"background_templates,omitempty" toml:"background_templates,omitempty"` + LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"` + TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"` + Template string `json:"template,omitempty" toml:"template,omitempty"` + Templates template.List `json:"templates,omitempty" toml:"templates,omitempty"` + TemplatesLogic template.Logic `json:"templates_logic,omitempty" toml:"templates_logic,omitempty"` + Properties properties.Map `json:"properties,omitempty" toml:"properties,omitempty"` + Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty"` + Alias string `json:"alias,omitempty" toml:"alias,omitempty"` + MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"` + MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` + Filler string `json:"filler,omitempty" toml:"filler,omitempty"` + + Enabled bool `json:"-" toml:"-"` + + Colors *terminal.Colors + Text string + + env platform.Environment + writer SegmentWriter + styleCache SegmentStyle + name string + + // debug info + Duration time.Duration + NameLength int +} + +func (segment *Segment) Name() string { + if len(segment.name) != 0 { + return segment.name + } + + name := segment.Alias + if len(name) == 0 { + name = c.Title(language.English).String(string(segment.Type)) + } + + segment.name = name + return name +} + +func (segment *Segment) SetEnabled(env platform.Environment) { + defer func() { + err := recover() + if err == nil { + return + } + // display a message explaining omp failed(with the err) + message := fmt.Sprintf("\noh-my-posh fatal error rendering %s segment:%s\n\n%s\n", segment.Type, err, debug.Stack()) + fmt.Println(message) + segment.Enabled = true + }() + + // segment timings for debug purposes + var start time.Time + if env.Flags().Debug { + start = time.Now() + segment.NameLength = len(segment.Name()) + defer func() { + segment.Duration = time.Since(start) + }() + } + + err := segment.MapSegmentWithWriter(env) + if err != nil || !segment.shouldIncludeFolder() { + return + } + + segment.env.DebugF("Segment: %s", segment.Name()) + + // validate toggles + if toggles, OK := segment.env.Cache().Get(platform.TOGGLECACHE); OK && len(toggles) > 0 { + list := strings.Split(toggles, ",") + for _, toggle := range list { + if SegmentType(toggle) == segment.Type || toggle == segment.Alias { + return + } + } + } + + if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) { + return + } + + if segment.writer.Enabled() { + segment.Enabled = true + env.TemplateCache().AddSegmentData(segment.Name(), segment.writer) + } +} + +func (segment *Segment) setText() { + if !segment.Enabled { + return + } + + segment.Text = segment.string() + segment.Enabled = len(strings.ReplaceAll(segment.Text, " ", "")) > 0 + + if !segment.Enabled { + segment.env.TemplateCache().RemoveSegmentData(segment.Name()) + } +} + +func (segment *Segment) string() string { + var templatesResult string + if !segment.Templates.Empty() { + templatesResult = segment.Templates.Resolve(segment.writer, segment.env, "", segment.TemplatesLogic) + if len(segment.Template) == 0 { + return templatesResult + } + } + + if len(segment.Template) == 0 { + segment.Template = segment.writer.Template() + } + + tmpl := &template.Text{ + Template: segment.Template, + Context: segment.writer, + Env: segment.env, + TemplatesResult: templatesResult, + } + + text, err := tmpl.Render() + if err != nil { + return err.Error() + } + + return text +} + +func (segment *Segment) shouldIncludeFolder() bool { + if segment.env == nil { + return true + } + + cwdIncluded := segment.cwdIncluded() + cwdExcluded := segment.cwdExcluded() + + return cwdIncluded && !cwdExcluded +} + +func (segment *Segment) cwdIncluded() bool { + value, ok := segment.Properties[properties.IncludeFolders] + if !ok { + // IncludeFolders isn't specified, everything is included + return true + } + + list := properties.ParseStringArray(value) + + if len(list) == 0 { + // IncludeFolders is an empty array, everything is included + return true + } + + return segment.env.DirMatchesOneOf(segment.env.Pwd(), list) +} + +func (segment *Segment) cwdExcluded() bool { + value, ok := segment.Properties[properties.ExcludeFolders] + if !ok { + value = segment.Properties[properties.IgnoreFolders] + } + + list := properties.ParseStringArray(value) + return segment.env.DirMatchesOneOf(segment.env.Pwd(), list) +} + +func (segment *Segment) ResolveForeground() string { + if segment.Colors == nil { + segment.Colors = &terminal.Colors{} + } + + 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) ResolveBackground() string { + if segment.Colors == nil { + segment.Colors = &terminal.Colors{} + } + + 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) ResolveStyle() SegmentStyle { + if len(segment.styleCache) != 0 { + return segment.styleCache + } + + segment.styleCache = segment.Style.resolve(segment.env, segment.writer) + + return segment.styleCache +} + +func (segment *Segment) IsPowerline() bool { + style := segment.ResolveStyle() + return style == Powerline || style == Accordion +} + +func (segment *Segment) HasEmptyDiamondAtEnd() bool { + if segment.ResolveStyle() != Diamond { + return false + } + + return len(segment.TrailingDiamond) == 0 +} diff --git a/src/engine/segment_test.go b/src/config/segment_test.go similarity index 96% rename from src/engine/segment_test.go rename to src/config/segment_test.go index fcb1da98..a54bd350 100644 --- a/src/engine/segment_test.go +++ b/src/config/segment_test.go @@ -1,4 +1,4 @@ -package engine +package config import ( "encoding/json" @@ -22,7 +22,7 @@ func TestMapSegmentWriterCanMap(t *testing.T) { Type: SESSION, } env := new(mock.MockedEnvironment) - err := sc.mapSegmentWithWriter(env) + err := sc.MapSegmentWithWriter(env) assert.NoError(t, err) assert.NotNil(t, sc.writer) } @@ -32,7 +32,7 @@ func TestMapSegmentWriterCannotMap(t *testing.T) { Type: "nilwriter", } env := new(mock.MockedEnvironment) - err := sc.mapSegmentWithWriter(env) + err := sc.MapSegmentWithWriter(env) assert.Error(t, err) } @@ -157,13 +157,13 @@ func TestGetColors(t *testing.T) { if tc.Background { segment.Background = tc.DefaultColor segment.BackgroundTemplates = tc.Templates - color := segment.background() + color := segment.ResolveBackground() assert.Equal(t, tc.ExpectedColor, color, tc.Case) continue } segment.Foreground = tc.DefaultColor segment.ForegroundTemplates = tc.Templates - color := segment.foreground() + color := segment.ResolveForeground() assert.Equal(t, tc.ExpectedColor, color, tc.Case) } } diff --git a/src/engine/segment.go b/src/config/segment_types.go similarity index 65% rename from src/engine/segment.go rename to src/config/segment_types.go index 032283f4..3cae8e07 100644 --- a/src/engine/segment.go +++ b/src/config/segment_types.go @@ -1,59 +1,15 @@ -package engine +package config import ( "errors" - "fmt" - "runtime/debug" - "strings" - "time" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/segments" - "github.com/jandedobbeleer/oh-my-posh/src/template" - "github.com/jandedobbeleer/oh-my-posh/src/terminal" - - c "golang.org/x/text/cases" - "golang.org/x/text/language" ) -// Segment represent a single segment and it's configuration -type Segment struct { - Type SegmentType `json:"type,omitempty" toml:"type,omitempty"` - Tips []string `json:"tips,omitempty" toml:"tips,omitempty"` - Style SegmentStyle `json:"style,omitempty" toml:"style,omitempty"` - PowerlineSymbol string `json:"powerline_symbol,omitempty" toml:"powerline_symbol,omitempty"` - LeadingPowerlineSymbol string `json:"leading_powerline_symbol,omitempty" toml:"leading_powerline_symbol,omitempty"` - InvertPowerline bool `json:"invert_powerline,omitempty" toml:"invert_powerline,omitempty"` - Foreground string `json:"foreground,omitempty" toml:"foreground,omitempty"` - ForegroundTemplates template.List `json:"foreground_templates,omitempty" toml:"foreground_templates,omitempty"` - Background string `json:"background,omitempty" toml:"background,omitempty"` - BackgroundTemplates template.List `json:"background_templates,omitempty" toml:"background_templates,omitempty"` - LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"` - TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"` - Template string `json:"template,omitempty" toml:"template,omitempty"` - Templates template.List `json:"templates,omitempty" toml:"templates,omitempty"` - TemplatesLogic template.Logic `json:"templates_logic,omitempty" toml:"templates_logic,omitempty"` - Properties properties.Map `json:"properties,omitempty" toml:"properties,omitempty"` - Interactive bool `json:"interactive,omitempty" toml:"interactive,omitempty"` - Alias string `json:"alias,omitempty" toml:"alias,omitempty"` - MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"` - MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` - Filler string `json:"filler,omitempty" toml:"filler,omitempty"` - - Enabled bool `json:"-" toml:"-"` - - colors *terminal.Colors - env platform.Environment - writer SegmentWriter - text string - styleCache SegmentStyle - name string - - // debug info - duration time.Duration - nameLength int -} +// SegmentType the type of segment, for more information, see the constants +type SegmentType string // SegmentWriter is the interface used to define what and if to write to the prompt type SegmentWriter interface { @@ -62,26 +18,6 @@ type SegmentWriter interface { Init(props properties.Properties, env platform.Environment) } -// SegmentStyle the style of segment, for more information, see the constants -type SegmentStyle string - -func (s *SegmentStyle) Resolve(env platform.Environment, context any) SegmentStyle { - txtTemplate := &template.Text{ - Context: context, - Env: env, - } - txtTemplate.Template = string(*s) - value, err := txtTemplate.Render() - // default to Plain - if err != nil || len(value) == 0 { - return Plain - } - return SegmentStyle(value) -} - -// SegmentType the type of segment, for more information, see the constants -type SegmentType string - const ( // Plain writes it without ornaments Plain SegmentStyle = "plain" @@ -372,102 +308,7 @@ var Segments = map[SegmentType]func() SegmentWriter{ YTM: func() SegmentWriter { return &segments.Ytm{} }, } -func (segment *Segment) style() SegmentStyle { - if len(segment.styleCache) != 0 { - return segment.styleCache - } - - segment.styleCache = segment.Style.Resolve(segment.env, segment.writer) - - return segment.styleCache -} - -func (segment *Segment) shouldIncludeFolder() bool { - if segment.env == nil { - return true - } - - cwdIncluded := segment.cwdIncluded() - cwdExcluded := segment.cwdExcluded() - - return cwdIncluded && !cwdExcluded -} - -func (segment *Segment) isPowerline() bool { - style := segment.style() - return style == Powerline || style == Accordion -} - -func (segment *Segment) hasEmptyDiamondAtEnd() bool { - if segment.style() != Diamond { - return false - } - - return len(segment.TrailingDiamond) == 0 -} - -func (segment *Segment) cwdIncluded() bool { - value, ok := segment.Properties[properties.IncludeFolders] - if !ok { - // IncludeFolders isn't specified, everything is included - return true - } - - list := properties.ParseStringArray(value) - - if len(list) == 0 { - // IncludeFolders is an empty array, everything is included - return true - } - - return segment.env.DirMatchesOneOf(segment.env.Pwd(), list) -} - -func (segment *Segment) cwdExcluded() bool { - value, ok := segment.Properties[properties.ExcludeFolders] - if !ok { - value = segment.Properties[properties.IgnoreFolders] - } - - list := properties.ParseStringArray(value) - return segment.env.DirMatchesOneOf(segment.env.Pwd(), list) -} - -func (segment *Segment) shouldInvokeWithTip(tip string) bool { - for _, t := range segment.Tips { - if t == tip { - return true - } - } - - return false -} - -func (segment *Segment) foreground() string { - if segment.colors == nil { - segment.colors = &terminal.Colors{} - } - - 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 { - if segment.colors == nil { - segment.colors = &terminal.Colors{} - } - - 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 { segment.env = env if segment.Properties == nil { @@ -487,107 +328,3 @@ func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error { return errors.New("unable to map writer") } - -func (segment *Segment) string() string { - var templatesResult string - if !segment.Templates.Empty() { - templatesResult = segment.Templates.Resolve(segment.writer, segment.env, "", segment.TemplatesLogic) - if len(segment.Template) == 0 { - return templatesResult - } - } - - if len(segment.Template) == 0 { - segment.Template = segment.writer.Template() - } - - tmpl := &template.Text{ - Template: segment.Template, - Context: segment.writer, - Env: segment.env, - TemplatesResult: templatesResult, - } - - text, err := tmpl.Render() - if err != nil { - return err.Error() - } - - return text -} - -func (segment *Segment) Name() string { - if len(segment.name) != 0 { - return segment.name - } - - name := segment.Alias - if len(name) == 0 { - name = c.Title(language.English).String(string(segment.Type)) - } - - segment.name = name - return name -} - -func (segment *Segment) SetEnabled(env platform.Environment) { - defer func() { - err := recover() - if err == nil { - return - } - // display a message explaining omp failed(with the err) - message := fmt.Sprintf("\noh-my-posh fatal error rendering %s segment:%s\n\n%s\n", segment.Type, err, debug.Stack()) - fmt.Println(message) - segment.Enabled = true - }() - - // segment timings for debug purposes - var start time.Time - if env.Flags().Debug { - start = time.Now() - segment.nameLength = len(segment.Name()) - defer func() { - segment.duration = time.Since(start) - }() - } - - err := segment.mapSegmentWithWriter(env) - if err != nil || !segment.shouldIncludeFolder() { - return - } - - segment.env.DebugF("Segment: %s", segment.Name()) - - // validate toggles - if toggles, OK := segment.env.Cache().Get(platform.TOGGLECACHE); OK && len(toggles) > 0 { - list := strings.Split(toggles, ",") - for _, toggle := range list { - if SegmentType(toggle) == segment.Type || toggle == segment.Alias { - return - } - } - } - - if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) { - return - } - - if segment.writer.Enabled() { - segment.Enabled = true - env.TemplateCache().AddSegmentData(segment.Name(), segment.writer) - } -} - -func (segment *Segment) SetText() { - if !segment.Enabled { - return - } - - segment.text = segment.string() - segment.Enabled = len(strings.ReplaceAll(segment.text, " ", "")) > 0 - - if !segment.Enabled { - segment.env.TemplateCache().RemoveSegmentData(segment.Name()) - } -} diff --git a/src/engine/block.go b/src/engine/block.go deleted file mode 100644 index 10f67961..00000000 --- a/src/engine/block.go +++ /dev/null @@ -1,328 +0,0 @@ -package engine - -import ( - "strings" - "sync" - - "github.com/jandedobbeleer/oh-my-posh/src/platform" - "github.com/jandedobbeleer/oh-my-posh/src/regex" - "github.com/jandedobbeleer/oh-my-posh/src/shell" - "github.com/jandedobbeleer/oh-my-posh/src/terminal" -) - -// BlockType type of block -type BlockType string - -// BlockAlignment aligment of a Block -type BlockAlignment string - -// Overflow defines how to handle a right block that overflows with the previous block -type Overflow string - -const ( - // Prompt writes one or more Segments - Prompt BlockType = "prompt" - // LineBreak creates a line break in the prompt - LineBreak BlockType = "newline" - // RPrompt is a right aligned prompt - RPrompt BlockType = "rprompt" - // Left aligns left - Left BlockAlignment = "left" - // Right aligns right - Right BlockAlignment = "right" - // Break adds a line break - Break Overflow = "break" - // Hide hides the block - Hide Overflow = "hide" -) - -// Block defines a part of the prompt with optional segments -type Block struct { - Type BlockType `json:"type,omitempty" toml:"type,omitempty"` - Alignment BlockAlignment `json:"alignment,omitempty" toml:"alignment,omitempty"` - Segments []*Segment `json:"segments,omitempty" toml:"segments,omitempty"` - Newline bool `json:"newline,omitempty" toml:"newline,omitempty"` - Filler string `json:"filler,omitempty" toml:"filler,omitempty"` - Overflow Overflow `json:"overflow,omitempty" toml:"overflow,omitempty"` - - LeadingDiamond string `json:"leading_diamond,omitempty" toml:"leading_diamond,omitempty"` - TrailingDiamond string `json:"trailing_diamond,omitempty" toml:"trailing_diamond,omitempty"` - - // Deprecated: keep the logic for legacy purposes - HorizontalOffset int `json:"horizontal_offset,omitempty" toml:"horizontal_offset,omitempty"` - VerticalOffset int `json:"vertical_offset,omitempty" toml:"vertical_offset,omitempty"` - - MaxWidth int `json:"max_width,omitempty" toml:"max_width,omitempty"` - MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` - - env platform.Environment - activeSegment *Segment - previousActiveSegment *Segment -} - -func (b *Block) Init(env platform.Environment) { - b.env = env - b.executeSegmentLogic() -} - -func (b *Block) InitPlain(env platform.Environment, config *Config) { - terminal.Init(shell.GENERIC) - terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, config.TerminalBackground) - terminal.AnsiColors = config.MakeColors() - - b.env = env - b.executeSegmentLogic() -} - -func (b *Block) executeSegmentLogic() { - if shouldHideForWidth(b.env, b.MinWidth, b.MaxWidth) { - return - } - - b.setEnabledSegments() - b.setSegmentsText() -} - -func (b *Block) setActiveSegment(segment *Segment) { - b.activeSegment = segment - terminal.Interactive = segment.Interactive - terminal.SetColors(segment.background(), segment.foreground()) -} - -func (b *Block) Enabled() bool { - if b.Type == LineBreak { - return true - } - - for _, segment := range b.Segments { - if segment.Enabled { - return true - } - } - - return false -} - -func (b *Block) setEnabledSegments() { - wg := sync.WaitGroup{} - wg.Add(len(b.Segments)) - defer wg.Wait() - - for _, segment := range b.Segments { - go func(s *Segment) { - defer wg.Done() - s.SetEnabled(b.env) - }(segment) - } -} - -func (b *Block) setSegmentsText() { - wg := sync.WaitGroup{} - wg.Add(len(b.Segments)) - defer wg.Wait() - for _, segment := range b.Segments { - go func(s *Segment) { - defer wg.Done() - s.SetText() - }(segment) - } -} - -func (b *Block) RenderSegments() (string, int) { - b.filterSegments() - - for i, segment := range b.Segments { - if colors, newCycle := cycle.Loop(); colors != nil { - cycle = &newCycle - segment.colors = colors - } - - if i == 0 && len(b.LeadingDiamond) > 0 { - segment.LeadingDiamond = b.LeadingDiamond - } - - if i == len(b.Segments)-1 && len(b.TrailingDiamond) > 0 { - segment.TrailingDiamond = b.TrailingDiamond - } - - b.setActiveSegment(segment) - b.renderActiveSegment() - } - - b.writeSeparator(true) - - return terminal.String() -} - -func (b *Block) filterSegments() { - segments := make([]*Segment, 0) - - for _, segment := range b.Segments { - if !segment.Enabled && segment.style() != Accordion { - continue - } - - segments = append(segments, segment) - } - - b.Segments = segments -} - -func (b *Block) renderActiveSegment() { - b.writeSeparator(false) - switch b.activeSegment.style() { - case Plain, Powerline: - terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) - case Diamond: - background := terminal.Transparent - if b.previousActiveSegment != nil && b.previousActiveSegment.hasEmptyDiamondAtEnd() { - background = b.previousActiveSegment.background() - } - terminal.Write(background, terminal.Background, b.activeSegment.LeadingDiamond) - terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) - case Accordion: - if b.activeSegment.Enabled { - terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) - } - } - b.previousActiveSegment = b.activeSegment - terminal.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground()) -} - -func (b *Block) writeSeparator(final bool) { - isCurrentDiamond := b.activeSegment.style() == Diamond - if final && isCurrentDiamond { - terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.TrailingDiamond) - return - } - - isPreviousDiamond := b.previousActiveSegment != nil && b.previousActiveSegment.style() == Diamond - if isPreviousDiamond { - b.adjustTrailingDiamondColorOverrides() - } - - if isPreviousDiamond && isCurrentDiamond && len(b.activeSegment.LeadingDiamond) == 0 { - terminal.Write(terminal.Background, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond) - return - } - - if isPreviousDiamond && len(b.previousActiveSegment.TrailingDiamond) > 0 { - terminal.Write(terminal.Transparent, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond) - } - - isPowerline := b.activeSegment.isPowerline() - - shouldOverridePowerlineLeadingSymbol := func() bool { - if !isPowerline { - return false - } - - if isPowerline && len(b.activeSegment.LeadingPowerlineSymbol) == 0 { - return false - } - - if b.previousActiveSegment != nil && b.previousActiveSegment.isPowerline() { - return false - } - - return true - } - - if shouldOverridePowerlineLeadingSymbol() { - terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.LeadingPowerlineSymbol) - return - } - - resolvePowerlineSymbol := func() string { - if isPowerline { - return b.activeSegment.PowerlineSymbol - } - - if b.previousActiveSegment != nil && b.previousActiveSegment.isPowerline() { - return b.previousActiveSegment.PowerlineSymbol - } - - return "" - } - - symbol := resolvePowerlineSymbol() - if len(symbol) == 0 { - return - } - - bgColor := terminal.Background - if final || !isPowerline { - bgColor = terminal.Transparent - } - - if b.activeSegment.style() == Diamond && len(b.activeSegment.LeadingDiamond) == 0 { - bgColor = terminal.Background - } - - if b.activeSegment.InvertPowerline { - terminal.Write(b.getPowerlineColor(), bgColor, symbol) - return - } - - terminal.Write(bgColor, b.getPowerlineColor(), symbol) -} - -func (b *Block) adjustTrailingDiamondColorOverrides() { - // as we now already adjusted the activeSegment, we need to change the value - // of background and foreground to parentBackground and parentForeground - // this will still break when using parentBackground and parentForeground as keywords - // in a trailing diamond, but let's fix that when it happens as it requires either a rewrite - // of the logic for diamonds or storing grandparents as well like one happy family. - if b.previousActiveSegment == nil || len(b.previousActiveSegment.TrailingDiamond) == 0 { - return - } - - if !strings.Contains(b.previousActiveSegment.TrailingDiamond, terminal.Background) && !strings.Contains(b.previousActiveSegment.TrailingDiamond, terminal.Foreground) { - return - } - - match := regex.FindNamedRegexMatch(terminal.AnchorRegex, b.previousActiveSegment.TrailingDiamond) - if len(match) == 0 { - return - } - - adjustOverride := func(anchor, override string) { - newOverride := override - switch override { - case terminal.Foreground: - newOverride = terminal.ParentForeground - case terminal.Background: - newOverride = terminal.ParentBackground - } - - if override == newOverride { - return - } - - newAnchor := strings.Replace(match[terminal.ANCHOR], override, newOverride, 1) - b.previousActiveSegment.TrailingDiamond = strings.Replace(b.previousActiveSegment.TrailingDiamond, anchor, newAnchor, 1) - } - - if len(match[terminal.BG]) > 0 { - adjustOverride(match[terminal.ANCHOR], match[terminal.BG]) - } - if len(match[terminal.FG]) > 0 { - adjustOverride(match[terminal.ANCHOR], match[terminal.FG]) - } -} - -func (b *Block) getPowerlineColor() string { - if b.previousActiveSegment == nil { - return terminal.Transparent - } - if b.previousActiveSegment.style() == Diamond && len(b.previousActiveSegment.TrailingDiamond) == 0 { - return b.previousActiveSegment.background() - } - if b.activeSegment.style() == Diamond && len(b.activeSegment.LeadingDiamond) == 0 { - return b.previousActiveSegment.background() - } - if !b.previousActiveSegment.isPowerline() { - return terminal.Transparent - } - return b.previousActiveSegment.background() -} diff --git a/src/engine/config.go b/src/engine/config.go deleted file mode 100644 index f021a976..00000000 --- a/src/engine/config.go +++ /dev/null @@ -1,525 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "time" - - json "github.com/goccy/go-json" - yaml "github.com/goccy/go-yaml" - "github.com/gookit/goutil/jsonutil" - "github.com/jandedobbeleer/oh-my-posh/src/platform" - "github.com/jandedobbeleer/oh-my-posh/src/properties" - "github.com/jandedobbeleer/oh-my-posh/src/segments" - "github.com/jandedobbeleer/oh-my-posh/src/shell" - "github.com/jandedobbeleer/oh-my-posh/src/template" - "github.com/jandedobbeleer/oh-my-posh/src/terminal" - toml "github.com/pelletier/go-toml/v2" -) - -const ( - JSON string = "json" - YAML string = "yaml" - TOML string = "toml" - - configVersion = 2 -) - -// Config holds all the theme for rendering the prompt -type Config struct { - Version int `json:"version" toml:"version"` - FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"` - ConsoleTitleTemplate string `json:"console_title_template,omitempty" toml:"console_title_template,omitempty"` - TerminalBackground string `json:"terminal_background,omitempty" toml:"terminal_background,omitempty"` - AccentColor string `json:"accent_color,omitempty" toml:"accent_color,omitempty"` - Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty"` - Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty"` - TransientPrompt *Segment `json:"transient_prompt,omitempty" toml:"transient_prompt,omitempty"` - ValidLine *Segment `json:"valid_line,omitempty" toml:"valid_line,omitempty"` - ErrorLine *Segment `json:"error_line,omitempty" toml:"error_line,omitempty"` - SecondaryPrompt *Segment `json:"secondary_prompt,omitempty" toml:"secondary_prompt,omitempty"` - DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"` - Palette terminal.Palette `json:"palette,omitempty" toml:"palette,omitempty"` - Palettes *terminal.Palettes `json:"palettes,omitempty" toml:"palettes,omitempty"` - Cycle terminal.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty"` - ShellIntegration bool `json:"shell_integration,omitempty" toml:"shell_integration,omitempty"` - PWD string `json:"pwd,omitempty" toml:"pwd,omitempty"` - Var map[string]any `json:"var,omitempty" toml:"var,omitempty"` - DisableCursorPositioning bool `json:"disable_cursor_positioning,omitempty" toml:"disable_cursor_positioning,omitempty"` - PatchPwshBleed bool `json:"patch_pwsh_bleed,omitempty" toml:"patch_pwsh_bleed,omitempty"` - DisableNotice bool `json:"disable_notice,omitempty" toml:"disable_notice,omitempty"` - AutoUpgrade bool `json:"auto_upgrade,omitempty" toml:"auto_upgrade,omitempty"` - ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty"` - - // Deprecated - OSC99 bool `json:"osc99,omitempty" toml:"osc99,omitempty"` - - Output string `json:"-" toml:"-"` - MigrateGlyphs bool `json:"-" toml:"-"` - Format string `json:"-" toml:"-"` - - origin string - // eval bool - updated bool - env platform.Environment -} - -// MakeColors creates instance of AnsiColors to use in AnsiWriter according to -// environment and configuration. -func (cfg *Config) MakeColors() terminal.ColorString { - cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" - return terminal.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) -} - -func (cfg *Config) getPalette() terminal.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 -} - -// LoadConfig returns the default configuration including possible user overrides -func LoadConfig(env platform.Environment) *Config { - cfg := loadConfig(env) - - // only migrate automatically when the switch isn't set - if !env.Flags().Migrate && cfg.Version < configVersion { - cfg.BackupAndMigrate() - } - - if !cfg.ShellIntegration { - return cfg - } - - // bash - ok - // fish - ok - // pwsh - ok - // zsh - ok - // cmd - ok, as of v1.4.25 (chrisant996/clink#457, fixed in chrisant996/clink@8a5d7ea) - // nu - built-in (and bugged) feature - nushell/nushell#5585, https://www.nushell.sh/blog/2022-08-16-nushell-0_67.html#shell-integration-fdncred-and-tyriar - // elv - broken OSC sequences - // xonsh - broken OSC sequences - // tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future - switch env.Shell() { - case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU: - cfg.ShellIntegration = false - } - - return cfg -} - -func loadConfig(env platform.Environment) *Config { - defer env.Trace(time.Now()) - configFile := env.Flags().Config - - if len(configFile) == 0 { - env.Debug("no config file specified, using default") - return defaultConfig(env, false) - } - - var cfg Config - cfg.origin = configFile - cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".") - cfg.env = env - - data, err := os.ReadFile(configFile) - if err != nil { - env.DebugF("error reading config file: %s", err) - return defaultConfig(env, true) - } - - switch cfg.Format { - case "yml", "yaml": - cfg.Format = YAML - err = yaml.Unmarshal(data, &cfg) - case "jsonc", "json": - cfg.Format = JSON - - str := jsonutil.StripComments(string(data)) - data = []byte(str) - - decoder := json.NewDecoder(bytes.NewReader(data)) - err = decoder.Decode(&cfg) - case "toml", "tml": - cfg.Format = TOML - err = toml.Unmarshal(data, &cfg) - default: - err = fmt.Errorf("unsupported config file format: %s", cfg.Format) - } - - if err != nil { - env.DebugF("error decoding config file: %s", err) - return defaultConfig(env, true) - } - - return &cfg -} - -func (cfg *Config) Export(format string) string { - if len(format) != 0 { - cfg.Format = format - } - - var result bytes.Buffer - - switch cfg.Format { - case YAML: - prefix := "# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" - yamlEncoder := yaml.NewEncoder(&result) - - err := yamlEncoder.Encode(cfg) - if err != nil { - return "" - } - - return prefix + result.String() - case JSON: - jsonEncoder := json.NewEncoder(&result) - jsonEncoder.SetEscapeHTML(false) - jsonEncoder.SetIndent("", " ") - _ = jsonEncoder.Encode(cfg) - prefix := "{\n \"$schema\": \"https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\"," - data := strings.Replace(result.String(), "{", prefix, 1) - return escapeGlyphs(data, cfg.MigrateGlyphs) - case TOML: - prefix := "#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json\n\n" - tomlEncoder := toml.NewEncoder(&result) - tomlEncoder.SetIndentTables(true) - - err := tomlEncoder.Encode(cfg) - if err != nil { - return "" - } - - return prefix + result.String() - } - - // unsupported format - return "" -} - -func (cfg *Config) BackupAndMigrate() { - cfg.Backup() - cfg.Migrate() - cfg.Write(cfg.Format) -} - -func (cfg *Config) Write(format string) { - content := cfg.Export(format) - if len(content) == 0 { - // we are unable to perform the export - os.Exit(65) - return - } - - destination := cfg.Output - if len(destination) == 0 { - destination = cfg.origin - } - - f, err := os.OpenFile(destination, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) - if err != nil { - return - } - - defer func() { - _ = f.Close() - }() - - _, err = f.WriteString(content) - if err != nil { - return - } -} - -func (cfg *Config) Backup() { - dst := cfg.origin + ".bak" - source, err := os.Open(cfg.origin) - if err != nil { - return - } - defer source.Close() - destination, err := os.Create(dst) - if err != nil { - return - } - defer destination.Close() - _, err = io.Copy(destination, source) - if err != nil { - return - } -} - -func escapeGlyphs(s string, migrate bool) string { - shouldExclude := func(r rune) bool { - if r < 0x1000 { // Basic Multilingual Plane - return true - } - if r > 0x1F600 && r < 0x1F64F { // Emoticons - return true - } - if r > 0x1F300 && r < 0x1F5FF { // Misc Symbols and Pictographs - return true - } - if r > 0x1F680 && r < 0x1F6FF { // Transport and Map - return true - } - if r > 0x2600 && r < 0x26FF { // Misc symbols - return true - } - if r > 0x2700 && r < 0x27BF { // Dingbats - return true - } - if r > 0xFE00 && r < 0xFE0F { // Variation Selectors - return true - } - if r > 0x1F900 && r < 0x1F9FF { // Supplemental Symbols and Pictographs - return true - } - if r > 0x1F1E6 && r < 0x1F1FF { // Flags - return true - } - return false - } - - var cp codePoints - var err error - if migrate { - cp, err = getGlyphCodePoints() - if err != nil { - migrate = false - } - } - - var builder strings.Builder - for _, r := range s { - // exclude regular characters and emojis - if shouldExclude(r) { - builder.WriteRune(r) - continue - } - - if migrate { - if val, OK := cp[uint64(r)]; OK { - r = rune(val) - } - } - - if r > 0x10000 { - // calculate surrogate pairs - one := 0xd800 + (((r - 0x10000) >> 10) & 0x3ff) - two := 0xdc00 + ((r - 0x10000) & 0x3ff) - quoted := fmt.Sprintf("\\u%04x\\u%04x", one, two) - builder.WriteString(quoted) - continue - } - - quoted := fmt.Sprintf("\\u%04x", r) - builder.WriteString(quoted) - } - return builder.String() -} - -func defaultConfig(env platform.Environment, warning bool) *Config { - exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}" - exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} " - if warning { - exitBackgroundTemplate = "p:red" - exitTemplate = " CONFIG ERROR " - } - cfg := &Config{ - Version: 2, - FinalSpace: true, - Blocks: []*Block{ - { - Type: Prompt, - Alignment: Left, - Segments: []*Segment{ - { - Type: SESSION, - Style: Diamond, - LeadingDiamond: "\ue0b6", - TrailingDiamond: "\ue0b0", - Background: "p:yellow", - Foreground: "p:black", - Template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} ", - }, - { - Type: PATH, - Style: Powerline, - PowerlineSymbol: "\ue0b0", - Background: "p:orange", - Foreground: "p:white", - Properties: properties.Map{ - properties.Style: "folder", - }, - Template: " \uea83 {{ path .Path .Location }} ", - }, - { - Type: GIT, - Style: Powerline, - PowerlineSymbol: "\ue0b0", - Background: "p:green", - BackgroundTemplates: []string{ - "{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}", - "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}", - "{{ if gt .Ahead 0 }}#49416D{{ end }}", - "{{ if gt .Behind 0 }}#7A306C{{ end }}", - }, - Foreground: "p:black", - ForegroundTemplates: []string{ - "{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}", - "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}", - "{{ if gt .Ahead 0 }}p:white{{ end }}", - }, - Properties: properties.Map{ - segments.BranchMaxLength: 25, - segments.FetchStatus: true, - segments.FetchUpstreamIcon: true, - }, - Template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} ", //nolint:lll - }, - { - Type: ROOT, - Style: Powerline, - PowerlineSymbol: "\ue0b0", - Background: "p:yellow", - Foreground: "p:white", - Template: " \uf0e7 ", - }, - { - Type: STATUS, - Style: Diamond, - LeadingDiamond: "\ue0b0", - TrailingDiamond: "\ue0b4", - Background: "p:blue", - BackgroundTemplates: []string{ - exitBackgroundTemplate, - }, - Foreground: "p:white", - Properties: properties.Map{ - properties.AlwaysEnabled: true, - }, - Template: exitTemplate, - }, - }, - }, - { - Type: RPrompt, - Segments: []*Segment{ - { - Type: NODE, - Style: Plain, - Background: "transparent", - Foreground: "p:green", - Template: "\ue718 ", - Properties: properties.Map{ - segments.HomeEnabled: false, - segments.FetchPackageManager: false, - segments.DisplayMode: "files", - }, - }, - { - Type: GOLANG, - Style: Plain, - Background: "transparent", - Foreground: "p:blue", - Template: "\ue626 ", - Properties: properties.Map{ - properties.FetchVersion: false, - }, - }, - { - Type: PYTHON, - Style: Plain, - Background: "transparent", - Foreground: "p:yellow", - Template: "\ue235 ", - Properties: properties.Map{ - properties.FetchVersion: false, - segments.DisplayMode: "files", - segments.FetchVirtualEnv: false, - }, - }, - { - Type: SHELL, - Style: Plain, - Background: "transparent", - Foreground: "p:white", - Template: "in {{ .Name }} ", - }, - { - Type: TIME, - Style: Plain, - Background: "transparent", - Foreground: "p:white", - Template: "at {{ .CurrentDate | date \"15:04:05\" }}", - }, - }, - }, - }, - ConsoleTitleTemplate: "{{ .Shell }} in {{ .Folder }}", - Palette: terminal.Palette{ - "black": "#262B44", - "blue": "#4B95E9", - "green": "#59C9A5", - "orange": "#F07623", - "red": "#D81E5B", - "white": "#E0DEF4", - "yellow": "#F3AE35", - }, - SecondaryPrompt: &Segment{ - Background: "transparent", - Foreground: "p:black", - Template: "\ue0b6<,p:yellow> > \ue0b0 ", - }, - TransientPrompt: &Segment{ - Background: "transparent", - Foreground: "p:black", - Template: "\ue0b6<,p:yellow> {{ .Folder }} \ue0b0 ", - }, - Tooltips: []*Segment{ - { - Type: AWS, - Style: Diamond, - LeadingDiamond: "\ue0b0", - TrailingDiamond: "\ue0b4", - Background: "p:orange", - Foreground: "p:white", - Template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} ", - Properties: properties.Map{ - properties.DisplayDefault: true, - }, - Tips: []string{"aws"}, - }, - { - Type: AZ, - Style: Diamond, - LeadingDiamond: "\ue0b0", - TrailingDiamond: "\ue0b4", - Background: "p:blue", - Foreground: "p:white", - Template: " \uebd8 {{ .Name }} ", - Properties: properties.Map{ - properties.DisplayDefault: true, - }, - Tips: []string{"az"}, - }, - }, - } - cfg.env = env - return cfg -} diff --git a/src/engine/debug.go b/src/engine/debug.go index fc8a51ea..38da86bb 100644 --- a/src/engine/debug.go +++ b/src/engine/debug.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/log" ) @@ -21,14 +22,14 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string { titleStartTime := time.Now() e.Env.Debug("Segment: Title") title := e.getTitleTemplateText() - consoleTitle := &Segment{ - name: "ConsoleTitle", - nameLength: 12, + consoleTitle := &config.Segment{ + Alias: "ConsoleTitle", + NameLength: 12, Enabled: len(e.Config.ConsoleTitleTemplate) > 0, - text: title, - duration: time.Since(titleStartTime), + Text: title, + Duration: time.Since(titleStartTime), } - largestSegmentNameLength := consoleTitle.nameLength + largestSegmentNameLength := consoleTitle.NameLength // render prompt e.write(log.Text("\nPrompt:\n\n").Green().Bold().Plain().String()) @@ -36,14 +37,14 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string { e.write(log.Text("\n\nSegments:\n\n").Green().Bold().Plain().String()) - var segments []*Segment + var segments []*config.Segment segments = append(segments, consoleTitle) for _, block := range e.Config.Blocks { for _, segment := range block.Segments { segments = append(segments, segment) - if segment.nameLength > largestSegmentNameLength { - largestSegmentNameLength = segment.nameLength + if segment.NameLength > largestSegmentNameLength { + largestSegmentNameLength = segment.NameLength } } } @@ -51,7 +52,7 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string { // 22 is the color for false/true and 7 is the reset color largestSegmentNameLength += 22 + 7 for _, segment := range segments { - duration := segment.duration.Milliseconds() + duration := segment.Duration.Milliseconds() var active log.Text if segment.Enabled { active = log.Text("true").Yellow() @@ -65,11 +66,12 @@ func (e *Engine) PrintDebug(startTime time.Time, version string) string { e.write(fmt.Sprintf("\n%s %s\n", log.Text("Run duration:").Green().Bold().Plain(), time.Since(startTime))) e.write(fmt.Sprintf("\n%s %s\n", log.Text("Cache path:").Green().Bold().Plain(), e.Env.CachePath())) - config := e.Env.Flags().Config - if len(config) == 0 { - config = "no --config set, using default built-in configuration" + cfg := e.Env.Flags().Config + if len(cfg) == 0 { + cfg = "no --config set, using default built-in configuration" } - e.write(fmt.Sprintf("\n%s %s\n", log.Text("Config path:").Green().Bold().Plain(), config)) + + e.write(fmt.Sprintf("\n%s %s\n", log.Text("Config path:").Green().Bold().Plain(), cfg)) e.write(log.Text("\nLogs:\n\n").Green().Bold().Plain().String()) e.write(e.Env.Logs()) diff --git a/src/engine/engine.go b/src/engine/engine.go index 48589b06..782c61f4 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -3,7 +3,9 @@ package engine import ( "strings" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/terminal" @@ -14,7 +16,7 @@ var ( ) type Engine struct { - Config *Config + Config *config.Config Env platform.Environment Plain bool @@ -22,6 +24,9 @@ type Engine struct { currentLineLength int rprompt string rpromptLength int + + activeSegment *config.Segment + previousActiveSegment *config.Segment } func (e *Engine) write(text string) { @@ -175,13 +180,13 @@ func (e *Engine) getTitleTemplateText() string { return "" } -func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { +func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool { defer e.patchPowerShellBleed() // This is deprecated but we leave it in to not break configs // It is encouraged to used "newline": true on block level // rather than the standalone the linebreak block - if block.Type == LineBreak { + if block.Type == config.LineBreak { // do not print a newline to avoid a leading space // when we're printin the first primary prompt in // the shell @@ -193,7 +198,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { // when in bash, for rprompt blocks we need to write plain // and wrap in escaped mode or the prompt will not render correctly - if e.Env.Shell() == shell.BASH && block.Type == RPrompt { + if e.Env.Shell() == shell.BASH && block.Type == config.RPrompt { block.InitPlain(e.Env, e.Config) } else { block.Init(e.Env) @@ -210,7 +215,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { e.newline() } - text, length := block.RenderSegments() + text, length := e.renderBlockSegments(block) // do not print anything when we don't have any text if length == 0 { @@ -218,18 +223,18 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { } switch block.Type { //nolint:exhaustive - case Prompt: + case config.Prompt: if block.VerticalOffset != 0 { e.write(terminal.ChangeLine(block.VerticalOffset)) } - if block.Alignment == Left { + if block.Alignment == config.Left { e.currentLineLength += length e.write(text) return true } - if block.Alignment != Right { + if block.Alignment != config.Right { return false } @@ -237,9 +242,9 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { // we can't print the right block as there's not enough room available if !OK { switch block.Overflow { - case Break: + case config.Break: e.newline() - case Hide: + case config.Hide: // make sure to fill if needed if padText, OK := e.shouldFill(block.Filler, space, 0); OK { e.write(padText) @@ -269,7 +274,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { prompt += text e.write(prompt) - case RPrompt: + case config.RPrompt: e.rprompt = text e.rpromptLength = length } @@ -281,7 +286,7 @@ func (e *Engine) patchPowerShellBleed() { // when in PowerShell, we need to clear the line after the prompt // to avoid the background being printed on the next line // when at the end of the buffer. - // See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65 + // See https://githue.com/JanDeDobbeleer/oh-my-posh/issues/65 if e.Env.Shell() != shell.PWSH && e.Env.Shell() != shell.PWSH5 { return } @@ -293,3 +298,216 @@ func (e *Engine) patchPowerShellBleed() { e.write(terminal.ClearAfter()) } + +func (e *Engine) renderBlockSegments(block *config.Block) (string, int) { + e.filterSegments(block) + + for i, segment := range block.Segments { + if colors, newCycle := cycle.Loop(); colors != nil { + cycle = &newCycle + segment.Colors = colors + } + + if i == 0 && len(block.LeadingDiamond) > 0 { + segment.LeadingDiamond = block.LeadingDiamond + } + + if i == len(block.Segments)-1 && len(block.TrailingDiamond) > 0 { + segment.TrailingDiamond = block.TrailingDiamond + } + + e.setActiveSegment(segment) + e.renderActiveSegment() + } + + e.writeSeparator(true) + + return terminal.String() +} + +func (e *Engine) filterSegments(block *config.Block) { + segments := make([]*config.Segment, 0) + + for _, segment := range block.Segments { + if !segment.Enabled && segment.ResolveStyle() != config.Accordion { + continue + } + + segments = append(segments, segment) + } + + block.Segments = segments +} + +func (e *Engine) setActiveSegment(segment *config.Segment) { + e.activeSegment = segment + terminal.Interactive = segment.Interactive + terminal.SetColors(segment.ResolveBackground(), segment.ResolveForeground()) +} + +func (e *Engine) renderActiveSegment() { + e.writeSeparator(false) + switch e.activeSegment.ResolveStyle() { + case config.Plain, config.Powerline: + terminal.Write(terminal.Background, terminal.Foreground, e.activeSegment.Text) + case config.Diamond: + background := terminal.Transparent + + if e.previousActiveSegment != nil && e.previousActiveSegment.HasEmptyDiamondAtEnd() { + background = e.previousActiveSegment.ResolveBackground() + } + + terminal.Write(background, terminal.Background, e.activeSegment.LeadingDiamond) + terminal.Write(terminal.Background, terminal.Foreground, e.activeSegment.Text) + case config.Accordion: + if e.activeSegment.Enabled { + terminal.Write(terminal.Background, terminal.Foreground, e.activeSegment.Text) + } + } + e.previousActiveSegment = e.activeSegment + + terminal.SetParentColors(e.previousActiveSegment.ResolveBackground(), e.previousActiveSegment.ResolveForeground()) +} + +func (e *Engine) writeSeparator(final bool) { + isCurrentDiamond := e.activeSegment.ResolveStyle() == config.Diamond + if final && isCurrentDiamond { + terminal.Write(terminal.Transparent, terminal.Background, e.activeSegment.TrailingDiamond) + return + } + + isPreviousDiamond := e.previousActiveSegment != nil && e.previousActiveSegment.ResolveStyle() == config.Diamond + if isPreviousDiamond { + e.adjustTrailingDiamondColorOverrides() + } + + if isPreviousDiamond && isCurrentDiamond && len(e.activeSegment.LeadingDiamond) == 0 { + terminal.Write(terminal.Background, terminal.ParentBackground, e.previousActiveSegment.TrailingDiamond) + return + } + + if isPreviousDiamond && len(e.previousActiveSegment.TrailingDiamond) > 0 { + terminal.Write(terminal.Transparent, terminal.ParentBackground, e.previousActiveSegment.TrailingDiamond) + } + + isPowerline := e.activeSegment.IsPowerline() + + shouldOverridePowerlineLeadingSymbol := func() bool { + if !isPowerline { + return false + } + + if isPowerline && len(e.activeSegment.LeadingPowerlineSymbol) == 0 { + return false + } + + if e.previousActiveSegment != nil && e.previousActiveSegment.IsPowerline() { + return false + } + + return true + } + + if shouldOverridePowerlineLeadingSymbol() { + terminal.Write(terminal.Transparent, terminal.Background, e.activeSegment.LeadingPowerlineSymbol) + return + } + + resolvePowerlineSymbol := func() string { + if isPowerline { + return e.activeSegment.PowerlineSymbol + } + + if e.previousActiveSegment != nil && e.previousActiveSegment.IsPowerline() { + return e.previousActiveSegment.PowerlineSymbol + } + + return "" + } + + symbol := resolvePowerlineSymbol() + if len(symbol) == 0 { + return + } + + bgColor := terminal.Background + if final || !isPowerline { + bgColor = terminal.Transparent + } + + if e.activeSegment.ResolveStyle() == config.Diamond && len(e.activeSegment.LeadingDiamond) == 0 { + bgColor = terminal.Background + } + + if e.activeSegment.InvertPowerline { + terminal.Write(e.getPowerlineColor(), bgColor, symbol) + return + } + + terminal.Write(bgColor, e.getPowerlineColor(), symbol) +} + +func (e *Engine) getPowerlineColor() string { + if e.previousActiveSegment == nil { + return terminal.Transparent + } + + if e.previousActiveSegment.ResolveStyle() == config.Diamond && len(e.previousActiveSegment.TrailingDiamond) == 0 { + return e.previousActiveSegment.ResolveBackground() + } + + if e.activeSegment.ResolveStyle() == config.Diamond && len(e.activeSegment.LeadingDiamond) == 0 { + return e.previousActiveSegment.ResolveBackground() + } + + if !e.previousActiveSegment.IsPowerline() { + return terminal.Transparent + } + + return e.previousActiveSegment.ResolveBackground() +} + +func (e *Engine) adjustTrailingDiamondColorOverrides() { + // as we now already adjusted the activeSegment, we need to change the value + // of background and foreground to parentBackground and parentForeground + // this will still break when using parentBackground and parentForeground as keywords + // in a trailing diamond, but let's fix that when it happens as it requires either a rewrite + // of the logic for diamonds or storing grandparents as well like one happy family. + if e.previousActiveSegment == nil || len(e.previousActiveSegment.TrailingDiamond) == 0 { + return + } + + if !strings.Contains(e.previousActiveSegment.TrailingDiamond, terminal.Background) && !strings.Contains(e.previousActiveSegment.TrailingDiamond, terminal.Foreground) { + return + } + + match := regex.FindNamedRegexMatch(terminal.AnchorRegex, e.previousActiveSegment.TrailingDiamond) + if len(match) == 0 { + return + } + + adjustOverride := func(anchor, override string) { + newOverride := override + switch override { + case terminal.Foreground: + newOverride = terminal.ParentForeground + case terminal.Background: + newOverride = terminal.ParentBackground + } + + if override == newOverride { + return + } + + newAnchor := strings.Replace(match[terminal.ANCHOR], override, newOverride, 1) + e.previousActiveSegment.TrailingDiamond = strings.Replace(e.previousActiveSegment.TrailingDiamond, anchor, newAnchor, 1) + } + + if len(match[terminal.BG]) > 0 { + adjustOverride(match[terminal.ANCHOR], match[terminal.BG]) + } + + if len(match[terminal.FG]) > 0 { + adjustOverride(match[terminal.ANCHOR], match[terminal.FG]) + } +} diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go index 0e0d5f90..4871ccf8 100644 --- a/src/engine/engine_test.go +++ b/src/engine/engine_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/mock" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/shell" @@ -89,7 +90,7 @@ func TestPrintPWD(t *testing.T) { engine := &Engine{ Env: env, - Config: &Config{ + Config: &config.Config{ PWD: tc.Config, OSC99: tc.OSC99, }, @@ -113,7 +114,7 @@ func engineRender() { env.Init() defer env.Close() - cfg := LoadConfig(env) + cfg := config.Load(env) terminal.Init(shell.GENERIC) terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground) @@ -189,7 +190,7 @@ func TestGetTitle(t *testing.T) { terminal.Init(shell.GENERIC) engine := &Engine{ - Config: &Config{ + Config: &config.Config{ ConsoleTitleTemplate: tc.Template, }, Env: env, @@ -250,7 +251,7 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { terminal.Init(shell.GENERIC) engine := &Engine{ - Config: &Config{ + Config: &config.Config{ ConsoleTitleTemplate: tc.Template, }, Env: env, diff --git a/src/engine/migrate_glyphs.go b/src/engine/migrate_glyphs.go deleted file mode 100644 index b5f0651a..00000000 --- a/src/engine/migrate_glyphs.go +++ /dev/null @@ -1,61 +0,0 @@ -package engine - -import ( - "context" - "io" - "net/http" - "strconv" - "strings" - "time" - - "github.com/jandedobbeleer/oh-my-posh/src/platform/net" -) - -type codePoints map[uint64]uint64 - -func getGlyphCodePoints() (codePoints, error) { - var codePoints = make(codePoints) - - ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000)) - defer cncl() - - request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://ohmyposh.dev/codepoints.csv", nil) - if err != nil { - return codePoints, &ConnectionError{reason: err.Error()} - } - - response, err := net.HTTPClient.Do(request) - if err != nil { - return codePoints, err - } - - defer response.Body.Close() - - bytes, err := io.ReadAll(response.Body) - if err != nil { - return codePoints, err - } - - lines := strings.Split(string(bytes), "\n") - - for _, line := range lines { - fields := strings.Split(line, ",") - if len(fields) < 2 { - continue - } - - oldGlyph, err := strconv.ParseUint(fields[0], 16, 32) - if err != nil { - continue - } - - newGlyph, err := strconv.ParseUint(fields[1], 16, 32) - if err != nil { - continue - } - - codePoints[oldGlyph] = newGlyph - } - - return codePoints, nil -} diff --git a/src/engine/migrate_glyphs_test.go b/src/engine/migrate_glyphs_test.go deleted file mode 100644 index 0b89c08b..00000000 --- a/src/engine/migrate_glyphs_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package engine - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetCodePoints(t *testing.T) { - codepoints, err := getGlyphCodePoints() - if connectionError, ok := err.(*ConnectionError); ok { - t.Log(connectionError.Error()) - return - } - assert.Equal(t, 1939, len(codepoints)) -} diff --git a/src/engine/new.go b/src/engine/new.go index 465748cf..eeee856a 100644 --- a/src/engine/new.go +++ b/src/engine/new.go @@ -1,6 +1,7 @@ package engine import ( + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/terminal" @@ -15,7 +16,7 @@ func New(flags *platform.Flags) *Engine { } env.Init() - cfg := LoadConfig(env) + cfg := config.Load(env) if cfg.PatchPwshBleed { patchPowerShellBleed(env.Shell(), flags) diff --git a/src/engine/prompt.go b/src/engine/prompt.go index 100442ea..06937f8c 100644 --- a/src/engine/prompt.go +++ b/src/engine/prompt.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/terminal" @@ -49,7 +50,7 @@ func (e *Engine) Primary() string { renderRPrompt = false } - if block.Type == RPrompt && !renderRPrompt { + if block.Type == config.RPrompt && !renderRPrompt { continue } @@ -121,7 +122,7 @@ func (e *Engine) Primary() string { func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { // populate env with latest context e.Env.LoadTemplateCache() - var prompt *Segment + var prompt *config.Segment switch promptType { case Debug: prompt = e.Config.DebugPrompt @@ -136,7 +137,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { } if prompt == nil { - prompt = &Segment{} + prompt = &config.Segment{} } getTemplate := func(template string) string { @@ -214,9 +215,9 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { } func (e *Engine) RPrompt() string { - filterRPromptBlock := func(blocks []*Block) *Block { + filterRPromptBlock := func(blocks []*config.Block) *config.Block { for _, block := range blocks { - if block.Type == RPrompt { + if block.Type == config.RPrompt { return block } } @@ -233,7 +234,7 @@ func (e *Engine) RPrompt() string { return "" } - text, length := block.RenderSegments() + text, length := e.renderBlockSegments(block) e.rpromptLength = length return text } diff --git a/src/engine/tooltip.go b/src/engine/tooltip.go index c76a65d2..0eb7075f 100644 --- a/src/engine/tooltip.go +++ b/src/engine/tooltip.go @@ -3,24 +3,23 @@ package engine import ( "strings" + "github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/terminal" ) func (e *Engine) Tooltip(tip string) string { tip = strings.Trim(tip, " ") - tooltips := make([]*Segment, 0, 1) + tooltips := make([]*config.Segment, 0, 1) for _, tooltip := range e.Config.Tooltips { - if !tooltip.shouldInvokeWithTip(tip) { + if !e.shouldInvokeWithTip(tooltip, tip) { continue } - if err := tooltip.mapSegmentWithWriter(e.Env); err != nil { - continue - } + tooltip.SetEnabled(e.Env) - if !tooltip.writer.Enabled() { + if !tooltip.Enabled { continue } @@ -32,8 +31,8 @@ func (e *Engine) Tooltip(tip string) string { } // little hack to reuse the current logic - block := &Block{ - Alignment: Right, + block := &config.Block{ + Alignment: config.Right, Segments: tooltips, } @@ -43,7 +42,7 @@ func (e *Engine) Tooltip(tip string) string { if !block.Enabled() { return "" } - text, _ := block.RenderSegments() + text, _ := e.renderBlockSegments(block) return text case shell.PWSH, shell.PWSH5: block.InitPlain(e.Env, e.Config) @@ -56,7 +55,7 @@ func (e *Engine) Tooltip(tip string) string { return "" } - text, length := block.RenderSegments() + text, length := e.renderBlockSegments(block) space := consoleWidth - e.Env.Flags().Column - length if space <= 0 { @@ -72,3 +71,13 @@ func (e *Engine) Tooltip(tip string) string { return "" } + +func (e *Engine) shouldInvokeWithTip(segment *config.Segment, tip string) bool { + for _, t := range segment.Tips { + if t == tip { + return true + } + } + + return false +}