From 482c9974136fef403b53935ccd4a6127c7efc536 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Fri, 6 May 2022 08:41:58 +0200 Subject: [PATCH] feat: reference segments properties cross segment resolves #2208 --- .vscode/launch.json | 2 +- src/cli/debug.go | 4 ++- src/engine/block.go | 52 +++++++++++++++++++++---------- src/engine/block_test.go | 2 +- src/engine/engine.go | 26 +++++++--------- src/engine/segment.go | 10 +++--- src/environment/concurrent_map.go | 16 ++++------ src/environment/shell.go | 26 +++++++++++++--- src/segments/iterm.go | 5 +-- src/template/text.go | 6 ++-- 10 files changed, 89 insertions(+), 60 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 239e746c..2318dd70 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "prompt", "print", "primary", - "--config=https://gist.githubusercontent.com/ehawman-rosenberg/50b3b695526f03690cfd396d02c2cca3/raw/c2e0bc78cdf551b0cc76f593e4fd70428f981c0e/eh-tea.omp.json", + "--config=~/.posh.omp.json", "--shell=pwsh", "--terminal-width=200", ] diff --git a/src/cli/debug.go b/src/cli/debug.go index b197c82c..67f06bc0 100644 --- a/src/cli/debug.go +++ b/src/cli/debug.go @@ -7,6 +7,7 @@ import ( "oh-my-posh/engine" "oh-my-posh/environment" "oh-my-posh/shell" + "time" "github.com/spf13/cobra" ) @@ -18,6 +19,7 @@ var debugCmd = &cobra.Command{ Long: "Print the prompt in debug mode.", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { + startTime := time.Now() env := &environment.ShellEnvironment{ CmdFlags: &environment.Flags{ Config: config, @@ -47,7 +49,7 @@ var debugCmd = &cobra.Command{ Ansi: ansi, Plain: plain, } - fmt.Print(eng.PrintDebug(cliVersion)) + fmt.Print(eng.PrintDebug(startTime, cliVersion)) }, } diff --git a/src/engine/block.go b/src/engine/block.go index abac7c86..4fd738d8 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -4,6 +4,7 @@ import ( "oh-my-posh/color" "oh-my-posh/environment" "oh-my-posh/shell" + "strings" "sync" "time" ) @@ -44,13 +45,15 @@ type Block struct { previousActiveSegment *Segment } -func (b *Block) init(env environment.Environment, writer color.Writer, ansi *color.Ansi) { +func (b *Block) Init(env environment.Environment, writer color.Writer, ansi *color.Ansi) { b.env = env b.writer = writer b.ansi = ansi + b.setEnabledSegments() + b.setSegmentsText() } -func (b *Block) initPlain(env environment.Environment, config *Config) { +func (b *Block) InitPlain(env environment.Environment, config *Config) { b.ansi = &color.Ansi{} b.ansi.InitPlain(env.Shell()) b.writer = &color.AnsiWriter{ @@ -59,6 +62,8 @@ func (b *Block) initPlain(env environment.Environment, config *Config) { AnsiColors: config.MakeColors(env), } b.env = env + b.setEnabledSegments() + b.setSegmentsText() } func (b *Block) setActiveSegment(segment *Segment) { @@ -66,7 +71,7 @@ func (b *Block) setActiveSegment(segment *Segment) { b.writer.SetColors(segment.background(), segment.foreground()) } -func (b *Block) enabled() bool { +func (b *Block) Enabled() bool { if b.Type == LineBreak { return true } @@ -78,14 +83,30 @@ func (b *Block) enabled() bool { return false } -func (b *Block) renderSegmentsText() { +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.renderText(b.env) + 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() + if !s.enabled { + return + } + s.text = s.string() + s.enabled = len(strings.ReplaceAll(s.text, " ", "")) > 0 }(segment) } } @@ -96,26 +117,26 @@ func (b *Block) renderSegments() (string, int) { if !segment.enabled && segment.Style != Accordion { continue } - b.renderSegment(segment) + b.setActiveSegment(segment) + b.renderActiveSegment() } b.writePowerline(true) b.writer.ClearParentColors() return b.writer.String() } -func (b *Block) renderSegment(segment *Segment) { - b.setActiveSegment(segment) +func (b *Block) renderActiveSegment() { b.writePowerline(false) switch b.activeSegment.Style { case Plain, Powerline: - b.writer.Write(color.Background, color.Foreground, segment.text) + b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) case Diamond: b.writer.Write(color.Transparent, color.Background, b.activeSegment.LeadingDiamond) - b.writer.Write(color.Background, color.Foreground, segment.text) + b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) b.writer.Write(color.Transparent, color.Background, b.activeSegment.TrailingDiamond) case Accordion: - if segment.enabled { - b.writer.Write(color.Background, color.Foreground, segment.text) + if b.activeSegment.enabled { + b.writer.Write(color.Background, color.Foreground, b.activeSegment.text) } } b.previousActiveSegment = b.activeSegment @@ -177,11 +198,10 @@ func (b *Block) debug() (int, []*SegmentTiming) { largestSegmentNameLength = segmentTiming.nameLength } start := time.Now() - segment.renderText(b.env) segmentTiming.active = segment.enabled - segmentTiming.text = segment.text - if segmentTiming.active { - b.renderSegment(segment) + if segmentTiming.active || segment.Style == Accordion { + b.setActiveSegment(segment) + b.renderActiveSegment() segmentTiming.text, _ = b.writer.String() b.writer.Reset() } diff --git a/src/engine/block_test.go b/src/engine/block_test.go index a1cb6a11..a17db81d 100644 --- a/src/engine/block_test.go +++ b/src/engine/block_test.go @@ -24,6 +24,6 @@ func TestBlockEnabled(t *testing.T) { Type: tc.Type, Segments: tc.Segments, } - assert.Equal(t, tc.Expected, block.enabled(), tc.Case) + assert.Equal(t, tc.Expected, block.Enabled(), tc.Case) } } diff --git a/src/engine/engine.go b/src/engine/engine.go index 26546c9f..907b00b1 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -107,12 +107,11 @@ func (e *Engine) renderBlock(block *Block) { // when in bash, for rprompt blocks we need to write plain // and wrap in escaped mode or the prompt will not render correctly if block.Type == RPrompt && e.Env.Shell() == shell.BASH { - block.initPlain(e.Env, e.Config) + block.InitPlain(e.Env, e.Config) } else { - block.init(e.Env, e.Writer, e.Ansi) + block.Init(e.Env, e.Writer, e.Ansi) } - block.renderSegmentsText() - if !block.enabled() { + if !block.Enabled() { return } if block.Newline { @@ -161,17 +160,16 @@ func (e *Engine) renderBlock(block *Block) { } // debug will loop through your config file and output the timings for each segments -func (e *Engine) PrintDebug(version string) string { +func (e *Engine) PrintDebug(startTime time.Time, version string) string { var segmentTimings []*SegmentTiming largestSegmentNameLength := 0 e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", version)) e.write("\n\x1b[1mSegments:\x1b[0m\n\n") // console title timing - start := time.Now() title := e.ConsoleTitle.GetTitle() title = strings.TrimPrefix(title, "\x1b]0;") title = strings.TrimSuffix(title, "\a") - duration := time.Since(start) + duration := time.Since(startTime) segmentTiming := &SegmentTiming{ name: "ConsoleTitle", nameLength: 12, @@ -182,7 +180,7 @@ func (e *Engine) PrintDebug(version string) string { segmentTimings = append(segmentTimings, segmentTiming) // loop each segments of each blocks for _, block := range e.Config.Blocks { - block.init(e.Env, e.Writer, e.Ansi) + block.Init(e.Env, e.Writer, e.Ansi) longestSegmentName, timings := block.debug() segmentTimings = append(segmentTimings, timings...) if longestSegmentName > largestSegmentNameLength { @@ -197,7 +195,7 @@ func (e *Engine) PrintDebug(version string) string { segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.active) e.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.text)) } - e.write(fmt.Sprintf("\n\x1b[1mRun duration:\x1b[0m %s\n", time.Since(start))) + e.write(fmt.Sprintf("\n\x1b[1mRun duration:\x1b[0m %s\n", time.Since(startTime))) e.write(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.Env.CachePath())) e.write("\n\x1b[1mLogs:\x1b[0m\n\n") e.write(e.Env.Logs()) @@ -245,7 +243,6 @@ func (e *Engine) PrintTooltip(tip string) string { if !tooltip.writer.Enabled() { return "" } - tooltip.text = tooltip.string() tooltip.enabled = true // little hack to reuse the current logic block := &Block{ @@ -254,11 +251,11 @@ func (e *Engine) PrintTooltip(tip string) string { } switch e.Env.Shell() { case shell.ZSH, shell.CMD, shell.FISH: - block.init(e.Env, e.Writer, e.Ansi) + block.Init(e.Env, e.Writer, e.Ansi) text, _ := block.renderSegments() return text case shell.PWSH, shell.PWSH5: - block.initPlain(e.Env, e.Config) + block.InitPlain(e.Env, e.Config) text, length := block.renderSegments() e.write(e.Ansi.ClearAfter()) e.write(e.Ansi.CarriageForward()) @@ -354,9 +351,8 @@ func (e *Engine) PrintRPrompt() string { if block == nil { return "" } - block.init(e.Env, e.Writer, e.Ansi) - block.renderSegmentsText() - if !block.enabled() { + block.Init(e.Env, e.Writer, e.Ansi) + if !block.Enabled() { return "" } text, length := block.renderSegments() diff --git a/src/engine/segment.go b/src/engine/segment.go index 9b0bdd5b..13e21e43 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -9,7 +9,6 @@ import ( "oh-my-posh/segments" "oh-my-posh/template" "runtime/debug" - "strings" "time" ) @@ -30,8 +29,8 @@ type Segment struct { Properties properties.Map `json:"properties,omitempty"` writer SegmentWriter - text string enabled bool + text string env environment.Environment backgroundCache string foregroundCache string @@ -326,7 +325,7 @@ func (segment *Segment) string() string { return text } -func (segment *Segment) renderText(env environment.Environment) { +func (segment *Segment) setEnabled(env environment.Environment) { defer func() { err := recover() if err == nil { @@ -335,7 +334,6 @@ func (segment *Segment) renderText(env environment.Environment) { // 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.text = "error" segment.enabled = true }() err := segment.mapSegmentWithWriter(env) @@ -343,7 +341,7 @@ func (segment *Segment) renderText(env environment.Environment) { return } if segment.writer.Enabled() { - segment.text = segment.string() - segment.enabled = len(strings.ReplaceAll(segment.text, " ", "")) > 0 + segment.enabled = true + env.TemplateCache().AddSegmentData(string(segment.Type), segment.writer) } } diff --git a/src/environment/concurrent_map.go b/src/environment/concurrent_map.go index c1109685..475d89a2 100644 --- a/src/environment/concurrent_map.go +++ b/src/environment/concurrent_map.go @@ -1,28 +1,24 @@ package environment -import "sync" - type concurrentMap struct { values map[string]interface{} - lock sync.RWMutex } func newConcurrentMap() *concurrentMap { return &concurrentMap{ values: make(map[string]interface{}), - lock: sync.RWMutex{}, } } func (c *concurrentMap) set(key string, value interface{}) { - c.lock.Lock() - defer c.lock.Unlock() + lock.Lock() + defer lock.Unlock() c.values[key] = value } func (c *concurrentMap) get(key string) (interface{}, bool) { - c.lock.RLock() - defer c.lock.RUnlock() + lock.RLock() + defer lock.RUnlock() if val, ok := c.values[key]; ok { return val, true } @@ -30,8 +26,8 @@ func (c *concurrentMap) get(key string) (interface{}, bool) { } func (c *concurrentMap) remove(key string) { - c.lock.RLock() - defer c.lock.RUnlock() + lock.RLock() + defer lock.RUnlock() delete(c.values, key) } diff --git a/src/environment/shell.go b/src/environment/shell.go index 9066f254..aa37521f 100644 --- a/src/environment/shell.go +++ b/src/environment/shell.go @@ -22,6 +22,8 @@ import ( "github.com/distatus/battery" process "github.com/shirou/gopsutil/v3/process" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) const ( @@ -31,6 +33,10 @@ const ( LinuxPlatform = "linux" ) +var ( + lock = sync.RWMutex{} +) + type Flags struct { ErrorCode int Config string @@ -120,6 +126,17 @@ type TemplateCache struct { Env map[string]string OS string WSL bool + Segments map[string]interface{} +} + +func (t *TemplateCache) AddSegmentData(key string, value interface{}) { + lock.Lock() + defer lock.Unlock() + if t.Segments == nil { + t.Segments = make(map[string]interface{}) + } + key = cases.Title(language.English).String(key) + t.Segments[key] = value } type BatteryInfo struct { @@ -207,7 +224,6 @@ type ShellEnvironment struct { tmplCache *TemplateCache logBuilder strings.Builder debug bool - lock sync.Mutex } func (env *ShellEnvironment) Init(debug bool) { @@ -311,9 +327,9 @@ func (env *ShellEnvironment) Getenv(key string) string { func (env *ShellEnvironment) Pwd() string { defer env.trace(time.Now(), "Pwd") - env.lock.Lock() + lock.Lock() defer func() { - env.lock.Unlock() + lock.Unlock() env.log(Debug, "Pwd", env.cwd) }() if env.cwd != "" { @@ -695,6 +711,8 @@ func (env *ShellEnvironment) TemplateCache() *TemplateCache { } func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (match bool) { + lock.Lock() + defer lock.Unlock() // sometimes the function panics inside golang, we want to silence that error // and assume that there's no match. Not perfect, but better than crashing // for the time being until we figure out what the actual root cause is @@ -705,8 +723,6 @@ func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (matc match = false } }() - env.lock.Lock() - defer env.lock.Unlock() match = dirMatchesOneOf(dir, env.Home(), env.GOOS(), regexes) return } diff --git a/src/segments/iterm.go b/src/segments/iterm.go index 8f24695a..df88b192 100644 --- a/src/segments/iterm.go +++ b/src/segments/iterm.go @@ -7,8 +7,9 @@ import ( ) type ITerm struct { - props properties.Properties - env environment.Environment + props properties.Properties + env environment.Environment + PromptMark string } diff --git a/src/template/text.go b/src/template/text.go index c93be852..03291209 100644 --- a/src/template/text.go +++ b/src/template/text.go @@ -25,7 +25,7 @@ type Text struct { type Data interface{} type Context struct { - environment.TemplateCache + *environment.TemplateCache // Simple container to hold ANY object Data @@ -34,7 +34,7 @@ type Context struct { func (c *Context) init(t *Text) { c.Data = t.Context if cache := t.Env.TemplateCache(); cache != nil { - c.TemplateCache = *cache + c.TemplateCache = cache return } } @@ -75,7 +75,7 @@ func (t *Text) cleanTemplate() { *knownVariables = append(*knownVariables, splitted[0]) return splitted[0], true } - knownVariables := []string{"Root", "PWD", "Folder", "Shell", "ShellVersion", "UserName", "HostName", "Env", "Data", "Code", "OS", "WSL"} + knownVariables := []string{"Root", "PWD", "Folder", "Shell", "ShellVersion", "UserName", "HostName", "Env", "Data", "Code", "OS", "WSL", "Segments"} matches := regex.FindAllNamedRegexMatch(`(?: |{|\()(?P(\.[a-zA-Z_][a-zA-Z0-9]*)+)`, t.Template) for _, match := range matches { if variable, OK := unknownVariable(match["var"], &knownVariables); OK {