package engine 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/shell" "github.com/jandedobbeleer/oh-my-posh/src/template" 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"` Tips []string `json:"tips,omitempty"` Style SegmentStyle `json:"style,omitempty"` PowerlineSymbol string `json:"powerline_symbol,omitempty"` InvertPowerline bool `json:"invert_powerline,omitempty"` Foreground string `json:"foreground,omitempty"` ForegroundTemplates template.List `json:"foreground_templates,omitempty"` Background string `json:"background,omitempty"` BackgroundTemplates template.List `json:"background_templates,omitempty"` LeadingDiamond string `json:"leading_diamond,omitempty"` TrailingDiamond string `json:"trailing_diamond,omitempty"` Template string `json:"template,omitempty"` Templates template.List `json:"templates,omitempty"` TemplatesLogic template.Logic `json:"templates_logic,omitempty"` Properties properties.Map `json:"properties,omitempty"` Interactive bool `json:"interactive,omitempty"` Alias string `json:"alias,omitempty"` MaxWidth int `json:"max_width,omitempty"` MinWidth int `json:"min_width,omitempty"` env platform.Environment writer SegmentWriter Enabled bool `json:"-"` text string backgroundCache string foregroundCache string styleCache SegmentStyle } // SegmentTiming holds the timing context for a segment type SegmentTiming struct { name string nameLength int active bool text string duration time.Duration } // SegmentWriter is the interface used to define what and if to write to the prompt type SegmentWriter interface { Enabled() bool Template() string 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 interface{}) 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" // Powerline writes it Powerline style Powerline SegmentStyle = "powerline" // Accordion writes it Powerline style but collapses the segment when disabled instead of hiding Accordion SegmentStyle = "accordion" // Diamond writes the prompt shaped with a leading and trailing symbol Diamond SegmentStyle = "diamond" // ANGULAR writes which angular cli version us currently active ANGULAR SegmentType = "angular" // AWS writes the active aws context AWS SegmentType = "aws" // AZ writes the Azure subscription info we're currently in AZ SegmentType = "az" // AZFUNC writes current AZ func version AZFUNC SegmentType = "azfunc" // BATTERY writes the battery percentage BATTERY SegmentType = "battery" // Brewfather segment BREWFATHER SegmentType = "brewfather" // cds (SAP CAP) version CDS SegmentType = "cds" // Cloud Foundry segment CF SegmentType = "cf" // Cloud Foundry logged in target CFTARGET SegmentType = "cftarget" // CMAKE writes the active cmake version CMAKE SegmentType = "cmake" // CMD writes the output of a shell command CMD SegmentType = "command" // CONNECTION writes a connection's information CONNECTION SegmentType = "connection" // CRYSTAL writes the active crystal version CRYSTAL SegmentType = "crystal" // DART writes the active dart version DART SegmentType = "dart" // DENO writes the active deno version DENO SegmentType = "deno" // DOTNET writes which dotnet version is currently active DOTNET SegmentType = "dotnet" // ELIXIR writes the elixir version ELIXIR SegmentType = "elixir" // EXECUTIONTIME writes the execution time of the last run command EXECUTIONTIME SegmentType = "executiontime" // EXIT writes the last exit code EXIT SegmentType = "exit" // FLUTTER writes the flutter version FLUTTER SegmentType = "flutter" // FOSSIL writes the fossil status FOSSIL SegmentType = "fossil" // GCP writes the active GCP context GCP SegmentType = "gcp" // GIT represents the git status and information GIT SegmentType = "git" // GITVERSION represents the gitversion information GITVERSION SegmentType = "gitversion" // GOLANG writes which go version is currently active GOLANG SegmentType = "go" // HASKELL segment HASKELL SegmentType = "haskell" // IPIFY segment IPIFY SegmentType = "ipify" // ITERM inserts the Shell Integration prompt mark on iTerm zsh/bash/fish ITERM SegmentType = "iterm" // JAVA writes the active java version JAVA SegmentType = "java" // JULIA writes which julia version is currently active JULIA SegmentType = "julia" // KOTLIN writes the active kotlin version KOTLIN SegmentType = "kotlin" // KUBECTL writes the Kubernetes context we're currently in KUBECTL SegmentType = "kubectl" // LUA writes the active lua version LUA SegmentType = "lua" // NBGV writes the nbgv version information NBGV SegmentType = "nbgv" // NIGHTSCOUT is an open source diabetes system NIGHTSCOUT SegmentType = "nightscout" // NODE writes which node version is currently active NODE SegmentType = "node" // npm version NPM SegmentType = "npm" // NX writes which Nx version us currently active NX SegmentType = "nx" // OS write os specific icon OS SegmentType = "os" // OWM writes the weather coming from openweatherdata OWM SegmentType = "owm" // PATH represents the current path segment PATH SegmentType = "path" // PERL writes which perl version is currently active PERL SegmentType = "perl" // PHP writes which php version is currently active PHP SegmentType = "php" // PLASTIC represents the plastic scm status and information PLASTIC SegmentType = "plastic" // Project version PROJECT SegmentType = "project" // PYTHON writes the virtual env name PYTHON SegmentType = "python" // R version R SegmentType = "r" // ROOT writes root symbol ROOT SegmentType = "root" // RUBY writes which ruby version is currently active RUBY SegmentType = "ruby" // RUST writes the cargo version information if cargo.toml is present RUST SegmentType = "rust" // SESSION represents the user info segment SESSION SegmentType = "session" // SHELL writes which shell we're currently in SHELL SegmentType = "shell" // SPOTIFY writes the SPOTIFY status for Mac SPOTIFY SegmentType = "spotify" // STRAVA is a sports activity tracker STRAVA SegmentType = "strava" // Subversion segment SVN SegmentType = "svn" // SWIFT writes the active swift version SWIFT SegmentType = "swift" // SYSTEMINFO writes system information (memory, cpu, load) SYSTEMINFO SegmentType = "sysinfo" // TERRAFORM writes the terraform workspace we're currently in TERRAFORM SegmentType = "terraform" // TEXT writes a text TEXT SegmentType = "text" // TIME writes the current timestamp TIME SegmentType = "time" // UI5 Tooling segment UI5TOOLING SegmentType = "ui5tooling" // WAKATIME writes tracked time spend in dev editors WAKATIME SegmentType = "wakatime" // WINREG queries the Windows registry. WINREG SegmentType = "winreg" // WITHINGS queries the Withings API. WITHINGS SegmentType = "withings" // XMAKE write the xmake version if xmake.lua is present XMAKE SegmentType = "xmake" // YTM writes YouTube Music information and status YTM SegmentType = "ytm" ) // Segments contains all available prompt segment writers. // Consumers of the library can also add their own segment writer. var Segments = map[SegmentType]func() SegmentWriter{ ANGULAR: func() SegmentWriter { return &segments.Angular{} }, AWS: func() SegmentWriter { return &segments.Aws{} }, AZ: func() SegmentWriter { return &segments.Az{} }, AZFUNC: func() SegmentWriter { return &segments.AzFunc{} }, BATTERY: func() SegmentWriter { return &segments.Battery{} }, BREWFATHER: func() SegmentWriter { return &segments.Brewfather{} }, CDS: func() SegmentWriter { return &segments.Cds{} }, CF: func() SegmentWriter { return &segments.Cf{} }, CFTARGET: func() SegmentWriter { return &segments.CfTarget{} }, CMD: func() SegmentWriter { return &segments.Cmd{} }, CONNECTION: func() SegmentWriter { return &segments.Connection{} }, CRYSTAL: func() SegmentWriter { return &segments.Crystal{} }, CMAKE: func() SegmentWriter { return &segments.Cmake{} }, DART: func() SegmentWriter { return &segments.Dart{} }, DENO: func() SegmentWriter { return &segments.Deno{} }, DOTNET: func() SegmentWriter { return &segments.Dotnet{} }, EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} }, ELIXIR: func() SegmentWriter { return &segments.Elixir{} }, EXIT: func() SegmentWriter { return &segments.Exit{} }, FLUTTER: func() SegmentWriter { return &segments.Flutter{} }, FOSSIL: func() SegmentWriter { return &segments.Fossil{} }, GCP: func() SegmentWriter { return &segments.Gcp{} }, GIT: func() SegmentWriter { return &segments.Git{} }, GITVERSION: func() SegmentWriter { return &segments.GitVersion{} }, GOLANG: func() SegmentWriter { return &segments.Golang{} }, HASKELL: func() SegmentWriter { return &segments.Haskell{} }, IPIFY: func() SegmentWriter { return &segments.IPify{} }, ITERM: func() SegmentWriter { return &segments.ITerm{} }, JAVA: func() SegmentWriter { return &segments.Java{} }, JULIA: func() SegmentWriter { return &segments.Julia{} }, KOTLIN: func() SegmentWriter { return &segments.Kotlin{} }, KUBECTL: func() SegmentWriter { return &segments.Kubectl{} }, LUA: func() SegmentWriter { return &segments.Lua{} }, NBGV: func() SegmentWriter { return &segments.Nbgv{} }, NIGHTSCOUT: func() SegmentWriter { return &segments.Nightscout{} }, NODE: func() SegmentWriter { return &segments.Node{} }, NPM: func() SegmentWriter { return &segments.Npm{} }, NX: func() SegmentWriter { return &segments.Nx{} }, OS: func() SegmentWriter { return &segments.Os{} }, OWM: func() SegmentWriter { return &segments.Owm{} }, PATH: func() SegmentWriter { return &segments.Path{} }, PERL: func() SegmentWriter { return &segments.Perl{} }, PHP: func() SegmentWriter { return &segments.Php{} }, PLASTIC: func() SegmentWriter { return &segments.Plastic{} }, PROJECT: func() SegmentWriter { return &segments.Project{} }, PYTHON: func() SegmentWriter { return &segments.Python{} }, R: func() SegmentWriter { return &segments.R{} }, ROOT: func() SegmentWriter { return &segments.Root{} }, RUBY: func() SegmentWriter { return &segments.Ruby{} }, RUST: func() SegmentWriter { return &segments.Rust{} }, SESSION: func() SegmentWriter { return &segments.Session{} }, SHELL: func() SegmentWriter { return &segments.Shell{} }, SPOTIFY: func() SegmentWriter { return &segments.Spotify{} }, STRAVA: func() SegmentWriter { return &segments.Strava{} }, SVN: func() SegmentWriter { return &segments.Svn{} }, SWIFT: func() SegmentWriter { return &segments.Swift{} }, SYSTEMINFO: func() SegmentWriter { return &segments.SystemInfo{} }, TERRAFORM: func() SegmentWriter { return &segments.Terraform{} }, TEXT: func() SegmentWriter { return &segments.Text{} }, TIME: func() SegmentWriter { return &segments.Time{} }, UI5TOOLING: func() SegmentWriter { return &segments.UI5Tooling{} }, WAKATIME: func() SegmentWriter { return &segments.Wakatime{} }, WINREG: func() SegmentWriter { return &segments.WindowsRegistry{} }, WITHINGS: func() SegmentWriter { return &segments.Withings{} }, XMAKE: func() SegmentWriter { return &segments.XMake{} }, 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) 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 len(segment.foregroundCache) == 0 { segment.foregroundCache = segment.ForegroundTemplates.FirstMatch(segment.writer, segment.env, segment.Foreground) } return segment.foregroundCache } func (segment *Segment) background() string { if len(segment.backgroundCache) == 0 { segment.backgroundCache = segment.BackgroundTemplates.FirstMatch(segment.writer, segment.env, segment.Background) } return segment.backgroundCache } func (segment *Segment) mapSegmentWithWriter(env platform.Environment) error { segment.env = env if segment.Properties == nil { segment.Properties = make(properties.Map) } if f, ok := Segments[segment.Type]; ok { writer := f() writer.Init(segment.Properties, env) segment.writer = writer return nil } 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) 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 }() err := segment.mapSegmentWithWriter(env) if err != nil || !segment.shouldIncludeFolder() { return } // 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 { return } } } if shouldHideForWidth(segment.env, segment.MinWidth, segment.MaxWidth) { return } if segment.writer.Enabled() { segment.Enabled = true name := segment.Alias if len(name) == 0 { name = c.Title(language.English).String(string(segment.Type)) } env.TemplateCache().AddSegmentData(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.Interactive { return } // we have to do this to prevent bash/zsh from misidentifying escape sequences switch segment.env.Shell() { case shell.BASH: segment.text = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(segment.text) case shell.ZSH: segment.text = strings.NewReplacer("`", "\\`", `%`, `%%`).Replace(segment.text) } }