feat: reference segments properties cross segment

resolves #2208
This commit is contained in:
Jan De Dobbeleer 2022-05-06 08:41:58 +02:00 committed by Jan De Dobbeleer
parent 88fb975d96
commit 482c997413
10 changed files with 89 additions and 60 deletions

2
.vscode/launch.json vendored
View file

@ -11,7 +11,7 @@
"prompt", "prompt",
"print", "print",
"primary", "primary",
"--config=https://gist.githubusercontent.com/ehawman-rosenberg/50b3b695526f03690cfd396d02c2cca3/raw/c2e0bc78cdf551b0cc76f593e4fd70428f981c0e/eh-tea.omp.json", "--config=~/.posh.omp.json",
"--shell=pwsh", "--shell=pwsh",
"--terminal-width=200", "--terminal-width=200",
] ]

View file

@ -7,6 +7,7 @@ import (
"oh-my-posh/engine" "oh-my-posh/engine"
"oh-my-posh/environment" "oh-my-posh/environment"
"oh-my-posh/shell" "oh-my-posh/shell"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -18,6 +19,7 @@ var debugCmd = &cobra.Command{
Long: "Print the prompt in debug mode.", Long: "Print the prompt in debug mode.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
startTime := time.Now()
env := &environment.ShellEnvironment{ env := &environment.ShellEnvironment{
CmdFlags: &environment.Flags{ CmdFlags: &environment.Flags{
Config: config, Config: config,
@ -47,7 +49,7 @@ var debugCmd = &cobra.Command{
Ansi: ansi, Ansi: ansi,
Plain: plain, Plain: plain,
} }
fmt.Print(eng.PrintDebug(cliVersion)) fmt.Print(eng.PrintDebug(startTime, cliVersion))
}, },
} }

View file

@ -4,6 +4,7 @@ import (
"oh-my-posh/color" "oh-my-posh/color"
"oh-my-posh/environment" "oh-my-posh/environment"
"oh-my-posh/shell" "oh-my-posh/shell"
"strings"
"sync" "sync"
"time" "time"
) )
@ -44,13 +45,15 @@ type Block struct {
previousActiveSegment *Segment 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.env = env
b.writer = writer b.writer = writer
b.ansi = ansi 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 = &color.Ansi{}
b.ansi.InitPlain(env.Shell()) b.ansi.InitPlain(env.Shell())
b.writer = &color.AnsiWriter{ b.writer = &color.AnsiWriter{
@ -59,6 +62,8 @@ func (b *Block) initPlain(env environment.Environment, config *Config) {
AnsiColors: config.MakeColors(env), AnsiColors: config.MakeColors(env),
} }
b.env = env b.env = env
b.setEnabledSegments()
b.setSegmentsText()
} }
func (b *Block) setActiveSegment(segment *Segment) { func (b *Block) setActiveSegment(segment *Segment) {
@ -66,7 +71,7 @@ func (b *Block) setActiveSegment(segment *Segment) {
b.writer.SetColors(segment.background(), segment.foreground()) b.writer.SetColors(segment.background(), segment.foreground())
} }
func (b *Block) enabled() bool { func (b *Block) Enabled() bool {
if b.Type == LineBreak { if b.Type == LineBreak {
return true return true
} }
@ -78,14 +83,30 @@ func (b *Block) enabled() bool {
return false return false
} }
func (b *Block) renderSegmentsText() { func (b *Block) setEnabledSegments() {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(len(b.Segments)) wg.Add(len(b.Segments))
defer wg.Wait() defer wg.Wait()
for _, segment := range b.Segments { for _, segment := range b.Segments {
go func(s *Segment) { go func(s *Segment) {
defer wg.Done() 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) }(segment)
} }
} }
@ -96,26 +117,26 @@ func (b *Block) renderSegments() (string, int) {
if !segment.enabled && segment.Style != Accordion { if !segment.enabled && segment.Style != Accordion {
continue continue
} }
b.renderSegment(segment) b.setActiveSegment(segment)
b.renderActiveSegment()
} }
b.writePowerline(true) b.writePowerline(true)
b.writer.ClearParentColors() b.writer.ClearParentColors()
return b.writer.String() return b.writer.String()
} }
func (b *Block) renderSegment(segment *Segment) { func (b *Block) renderActiveSegment() {
b.setActiveSegment(segment)
b.writePowerline(false) b.writePowerline(false)
switch b.activeSegment.Style { switch b.activeSegment.Style {
case Plain, Powerline: case Plain, Powerline:
b.writer.Write(color.Background, color.Foreground, segment.text) b.writer.Write(color.Background, color.Foreground, b.activeSegment.text)
case Diamond: case Diamond:
b.writer.Write(color.Transparent, color.Background, b.activeSegment.LeadingDiamond) 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) b.writer.Write(color.Transparent, color.Background, b.activeSegment.TrailingDiamond)
case Accordion: case Accordion:
if segment.enabled { if b.activeSegment.enabled {
b.writer.Write(color.Background, color.Foreground, segment.text) b.writer.Write(color.Background, color.Foreground, b.activeSegment.text)
} }
} }
b.previousActiveSegment = b.activeSegment b.previousActiveSegment = b.activeSegment
@ -177,11 +198,10 @@ func (b *Block) debug() (int, []*SegmentTiming) {
largestSegmentNameLength = segmentTiming.nameLength largestSegmentNameLength = segmentTiming.nameLength
} }
start := time.Now() start := time.Now()
segment.renderText(b.env)
segmentTiming.active = segment.enabled segmentTiming.active = segment.enabled
segmentTiming.text = segment.text if segmentTiming.active || segment.Style == Accordion {
if segmentTiming.active { b.setActiveSegment(segment)
b.renderSegment(segment) b.renderActiveSegment()
segmentTiming.text, _ = b.writer.String() segmentTiming.text, _ = b.writer.String()
b.writer.Reset() b.writer.Reset()
} }

View file

@ -24,6 +24,6 @@ func TestBlockEnabled(t *testing.T) {
Type: tc.Type, Type: tc.Type,
Segments: tc.Segments, Segments: tc.Segments,
} }
assert.Equal(t, tc.Expected, block.enabled(), tc.Case) assert.Equal(t, tc.Expected, block.Enabled(), tc.Case)
} }
} }

View file

@ -107,12 +107,11 @@ func (e *Engine) renderBlock(block *Block) {
// when in bash, for rprompt blocks we need to write plain // when in bash, for rprompt blocks we need to write plain
// and wrap in escaped mode or the prompt will not render correctly // and wrap in escaped mode or the prompt will not render correctly
if block.Type == RPrompt && e.Env.Shell() == shell.BASH { if block.Type == RPrompt && e.Env.Shell() == shell.BASH {
block.initPlain(e.Env, e.Config) block.InitPlain(e.Env, e.Config)
} else { } 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 return
} }
if block.Newline { 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 // 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 var segmentTimings []*SegmentTiming
largestSegmentNameLength := 0 largestSegmentNameLength := 0
e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", version)) e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", version))
e.write("\n\x1b[1mSegments:\x1b[0m\n\n") e.write("\n\x1b[1mSegments:\x1b[0m\n\n")
// console title timing // console title timing
start := time.Now()
title := e.ConsoleTitle.GetTitle() title := e.ConsoleTitle.GetTitle()
title = strings.TrimPrefix(title, "\x1b]0;") title = strings.TrimPrefix(title, "\x1b]0;")
title = strings.TrimSuffix(title, "\a") title = strings.TrimSuffix(title, "\a")
duration := time.Since(start) duration := time.Since(startTime)
segmentTiming := &SegmentTiming{ segmentTiming := &SegmentTiming{
name: "ConsoleTitle", name: "ConsoleTitle",
nameLength: 12, nameLength: 12,
@ -182,7 +180,7 @@ func (e *Engine) PrintDebug(version string) string {
segmentTimings = append(segmentTimings, segmentTiming) segmentTimings = append(segmentTimings, segmentTiming)
// loop each segments of each blocks // loop each segments of each blocks
for _, block := range e.Config.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() longestSegmentName, timings := block.debug()
segmentTimings = append(segmentTimings, timings...) segmentTimings = append(segmentTimings, timings...)
if longestSegmentName > largestSegmentNameLength { if longestSegmentName > largestSegmentNameLength {
@ -197,7 +195,7 @@ func (e *Engine) PrintDebug(version string) string {
segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.active) 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("%-*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(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.Env.CachePath()))
e.write("\n\x1b[1mLogs:\x1b[0m\n\n") e.write("\n\x1b[1mLogs:\x1b[0m\n\n")
e.write(e.Env.Logs()) e.write(e.Env.Logs())
@ -245,7 +243,6 @@ func (e *Engine) PrintTooltip(tip string) string {
if !tooltip.writer.Enabled() { if !tooltip.writer.Enabled() {
return "" return ""
} }
tooltip.text = tooltip.string()
tooltip.enabled = true tooltip.enabled = true
// little hack to reuse the current logic // little hack to reuse the current logic
block := &Block{ block := &Block{
@ -254,11 +251,11 @@ func (e *Engine) PrintTooltip(tip string) string {
} }
switch e.Env.Shell() { switch e.Env.Shell() {
case shell.ZSH, shell.CMD, shell.FISH: 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() text, _ := block.renderSegments()
return text return text
case shell.PWSH, shell.PWSH5: case shell.PWSH, shell.PWSH5:
block.initPlain(e.Env, e.Config) block.InitPlain(e.Env, e.Config)
text, length := block.renderSegments() text, length := block.renderSegments()
e.write(e.Ansi.ClearAfter()) e.write(e.Ansi.ClearAfter())
e.write(e.Ansi.CarriageForward()) e.write(e.Ansi.CarriageForward())
@ -354,9 +351,8 @@ func (e *Engine) PrintRPrompt() string {
if block == nil { if block == nil {
return "" return ""
} }
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 "" return ""
} }
text, length := block.renderSegments() text, length := block.renderSegments()

View file

@ -9,7 +9,6 @@ import (
"oh-my-posh/segments" "oh-my-posh/segments"
"oh-my-posh/template" "oh-my-posh/template"
"runtime/debug" "runtime/debug"
"strings"
"time" "time"
) )
@ -30,8 +29,8 @@ type Segment struct {
Properties properties.Map `json:"properties,omitempty"` Properties properties.Map `json:"properties,omitempty"`
writer SegmentWriter writer SegmentWriter
text string
enabled bool enabled bool
text string
env environment.Environment env environment.Environment
backgroundCache string backgroundCache string
foregroundCache string foregroundCache string
@ -326,7 +325,7 @@ func (segment *Segment) string() string {
return text return text
} }
func (segment *Segment) renderText(env environment.Environment) { func (segment *Segment) setEnabled(env environment.Environment) {
defer func() { defer func() {
err := recover() err := recover()
if err == nil { if err == nil {
@ -335,7 +334,6 @@ func (segment *Segment) renderText(env environment.Environment) {
// display a message explaining omp failed(with the err) // 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()) message := fmt.Sprintf("\noh-my-posh fatal error rendering %s segment:%s\n\n%s\n", segment.Type, err, debug.Stack())
fmt.Println(message) fmt.Println(message)
segment.text = "error"
segment.enabled = true segment.enabled = true
}() }()
err := segment.mapSegmentWithWriter(env) err := segment.mapSegmentWithWriter(env)
@ -343,7 +341,7 @@ func (segment *Segment) renderText(env environment.Environment) {
return return
} }
if segment.writer.Enabled() { if segment.writer.Enabled() {
segment.text = segment.string() segment.enabled = true
segment.enabled = len(strings.ReplaceAll(segment.text, " ", "")) > 0 env.TemplateCache().AddSegmentData(string(segment.Type), segment.writer)
} }
} }

View file

@ -1,28 +1,24 @@
package environment package environment
import "sync"
type concurrentMap struct { type concurrentMap struct {
values map[string]interface{} values map[string]interface{}
lock sync.RWMutex
} }
func newConcurrentMap() *concurrentMap { func newConcurrentMap() *concurrentMap {
return &concurrentMap{ return &concurrentMap{
values: make(map[string]interface{}), values: make(map[string]interface{}),
lock: sync.RWMutex{},
} }
} }
func (c *concurrentMap) set(key string, value interface{}) { func (c *concurrentMap) set(key string, value interface{}) {
c.lock.Lock() lock.Lock()
defer c.lock.Unlock() defer lock.Unlock()
c.values[key] = value c.values[key] = value
} }
func (c *concurrentMap) get(key string) (interface{}, bool) { func (c *concurrentMap) get(key string) (interface{}, bool) {
c.lock.RLock() lock.RLock()
defer c.lock.RUnlock() defer lock.RUnlock()
if val, ok := c.values[key]; ok { if val, ok := c.values[key]; ok {
return val, true return val, true
} }
@ -30,8 +26,8 @@ func (c *concurrentMap) get(key string) (interface{}, bool) {
} }
func (c *concurrentMap) remove(key string) { func (c *concurrentMap) remove(key string) {
c.lock.RLock() lock.RLock()
defer c.lock.RUnlock() defer lock.RUnlock()
delete(c.values, key) delete(c.values, key)
} }

View file

@ -22,6 +22,8 @@ import (
"github.com/distatus/battery" "github.com/distatus/battery"
process "github.com/shirou/gopsutil/v3/process" process "github.com/shirou/gopsutil/v3/process"
"golang.org/x/text/cases"
"golang.org/x/text/language"
) )
const ( const (
@ -31,6 +33,10 @@ const (
LinuxPlatform = "linux" LinuxPlatform = "linux"
) )
var (
lock = sync.RWMutex{}
)
type Flags struct { type Flags struct {
ErrorCode int ErrorCode int
Config string Config string
@ -120,6 +126,17 @@ type TemplateCache struct {
Env map[string]string Env map[string]string
OS string OS string
WSL bool 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 { type BatteryInfo struct {
@ -207,7 +224,6 @@ type ShellEnvironment struct {
tmplCache *TemplateCache tmplCache *TemplateCache
logBuilder strings.Builder logBuilder strings.Builder
debug bool debug bool
lock sync.Mutex
} }
func (env *ShellEnvironment) Init(debug bool) { func (env *ShellEnvironment) Init(debug bool) {
@ -311,9 +327,9 @@ func (env *ShellEnvironment) Getenv(key string) string {
func (env *ShellEnvironment) Pwd() string { func (env *ShellEnvironment) Pwd() string {
defer env.trace(time.Now(), "Pwd") defer env.trace(time.Now(), "Pwd")
env.lock.Lock() lock.Lock()
defer func() { defer func() {
env.lock.Unlock() lock.Unlock()
env.log(Debug, "Pwd", env.cwd) env.log(Debug, "Pwd", env.cwd)
}() }()
if env.cwd != "" { if env.cwd != "" {
@ -695,6 +711,8 @@ func (env *ShellEnvironment) TemplateCache() *TemplateCache {
} }
func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (match bool) { 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 // 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 // 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 // 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 match = false
} }
}() }()
env.lock.Lock()
defer env.lock.Unlock()
match = dirMatchesOneOf(dir, env.Home(), env.GOOS(), regexes) match = dirMatchesOneOf(dir, env.Home(), env.GOOS(), regexes)
return return
} }

View file

@ -9,6 +9,7 @@ import (
type ITerm struct { type ITerm struct {
props properties.Properties props properties.Properties
env environment.Environment env environment.Environment
PromptMark string PromptMark string
} }

View file

@ -25,7 +25,7 @@ type Text struct {
type Data interface{} type Data interface{}
type Context struct { type Context struct {
environment.TemplateCache *environment.TemplateCache
// Simple container to hold ANY object // Simple container to hold ANY object
Data Data
@ -34,7 +34,7 @@ type Context struct {
func (c *Context) init(t *Text) { func (c *Context) init(t *Text) {
c.Data = t.Context c.Data = t.Context
if cache := t.Env.TemplateCache(); cache != nil { if cache := t.Env.TemplateCache(); cache != nil {
c.TemplateCache = *cache c.TemplateCache = cache
return return
} }
} }
@ -75,7 +75,7 @@ func (t *Text) cleanTemplate() {
*knownVariables = append(*knownVariables, splitted[0]) *knownVariables = append(*knownVariables, splitted[0])
return splitted[0], true 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<var>(\.[a-zA-Z_][a-zA-Z0-9]*)+)`, t.Template) matches := regex.FindAllNamedRegexMatch(`(?: |{|\()(?P<var>(\.[a-zA-Z_][a-zA-Z0-9]*)+)`, t.Template)
for _, match := range matches { for _, match := range matches {
if variable, OK := unknownVariable(match["var"], &knownVariables); OK { if variable, OK := unknownVariable(match["var"], &knownVariables); OK {