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",
"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",
]

View file

@ -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))
},
}

View file

@ -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()
}

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

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

View file

@ -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<var>(\.[a-zA-Z_][a-zA-Z0-9]*)+)`, t.Template)
for _, match := range matches {
if variable, OK := unknownVariable(match["var"], &knownVariables); OK {