refactor: move TemplateCache to template package
Some checks are pending
Code QL / code-ql (push) Waiting to run
Release / changelog (push) Waiting to run
Release / artifacts (push) Blocked by required conditions

This commit is contained in:
Jan De Dobbeleer 2024-11-12 08:42:35 +01:00 committed by Jan De Dobbeleer
parent 1a313f48ec
commit 077135e6cc
61 changed files with 2281 additions and 2176 deletions

61
src/cache/path.go vendored Normal file
View file

@ -0,0 +1,61 @@
package cache
import (
"os"
"path/filepath"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
)
func Path() string {
defer log.Trace(time.Now())
returnOrBuildCachePath := func(input string) (string, bool) {
// validate root path
if _, err := os.Stat(input); err != nil {
return "", false
}
// validate oh-my-posh folder, if non existent, create it
cachePath := filepath.Join(input, "oh-my-posh")
if _, err := os.Stat(cachePath); err == nil {
return cachePath, true
}
if err := os.Mkdir(cachePath, 0o755); err != nil {
return "", false
}
return cachePath, true
}
// allow the user to set the cache path using OMP_CACHE_DIR
if cachePath, OK := returnOrBuildCachePath(os.Getenv("OMP_CACHE_DIR")); OK {
return cachePath
}
// WINDOWS cache folder, should not exist elsewhere
if cachePath, OK := returnOrBuildCachePath(os.Getenv("LOCALAPPDATA")); OK {
return cachePath
}
// get XDG_CACHE_HOME if present
if cachePath, OK := returnOrBuildCachePath(os.Getenv("XDG_CACHE_HOME")); OK {
return cachePath
}
// try to create the cache folder in the user's home directory if non-existent
dotCache := filepath.Join(path.Home(), ".cache")
if _, err := os.Stat(dotCache); err != nil {
_ = os.Mkdir(dotCache, 0o755)
}
// HOME cache folder
if cachePath, OK := returnOrBuildCachePath(dotCache); OK {
return cachePath
}
return path.Home()
}

View file

@ -6,7 +6,6 @@ import (
"path/filepath" "path/filepath"
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -34,18 +33,11 @@ You can do the following:
return return
} }
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{},
}
env.Init()
defer env.Close()
switch args[0] { switch args[0] {
case "path": case "path":
fmt.Println(env.CachePath()) fmt.Println(cache.Path())
case "clear": case "clear":
deletedFiles, err := cache.Clear(env.CachePath(), true) deletedFiles, err := cache.Clear(cache.Path(), true)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
@ -55,7 +47,7 @@ You can do the following:
fmt.Println("removed cache file:", file) fmt.Println("removed cache file:", file)
} }
case "edit": case "edit":
cacheFilePath := filepath.Join(env.CachePath(), cache.FileName) cacheFilePath := filepath.Join(cache.Path(), cache.FileName)
os.Exit(editFileWithEditor(cacheFilePath)) os.Exit(editFileWithEditor(cacheFilePath))
} }
}, },

View file

@ -5,7 +5,7 @@ import (
"os" "os"
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -31,13 +31,8 @@ You can export, migrate or edit the config (via the editor specified in the envi
} }
switch args[0] { switch args[0] {
case "edit": case "edit":
env := &runtime.Terminal{ path := config.Path((configFlag))
CmdFlags: &runtime.Flags{ os.Exit(editFileWithEditor(path))
Config: configFlag,
},
}
env.ResolveConfigPath()
os.Exit(editFileWithEditor(env.CmdFlags.Config))
case "get": case "get":
// only here for backwards compatibility // only here for backwards compatibility
fmt.Print(time.Now().UnixNano() / 1000000) fmt.Print(time.Now().UnixNano() / 1000000)

View file

@ -7,7 +7,8 @@ import (
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -39,14 +40,7 @@ Exports the current config to "~/new_config.omp.json" (in JSON format).`,
os.Exit(2) os.Exit(2)
} }
env := &runtime.Terminal{ cfg := config.Load(configFlag, shell.GENERIC, false)
CmdFlags: &runtime.Flags{
Config: configFlag,
},
}
env.Init()
defer env.Close()
cfg := config.Load(env)
validateExportFormat := func() { validateExportFormat := func() {
format = strings.ToLower(format) format = strings.ToLower(format)
@ -74,7 +68,7 @@ Exports the current config to "~/new_config.omp.json" (in JSON format).`,
return return
} }
cfg.Output = cleanOutputPath(output, env) cfg.Output = cleanOutputPath(output)
if len(format) == 0 { if len(format) == 0 {
format = strings.TrimPrefix(filepath.Ext(output), ".") format = strings.TrimPrefix(filepath.Ext(output), ".")
@ -85,16 +79,16 @@ Exports the current config to "~/new_config.omp.json" (in JSON format).`,
}, },
} }
func cleanOutputPath(path string, env runtime.Environment) string { func cleanOutputPath(output string) string {
path = runtime.ReplaceTildePrefixWithHomeDir(env, path) output = path.ReplaceTildePrefixWithHomeDir(output)
if !filepath.IsAbs(path) { if !filepath.IsAbs(output) {
if absPath, err := filepath.Abs(path); err == nil { if absPath, err := filepath.Abs(output); err == nil {
path = absPath output = absPath
} }
} }
return filepath.Clean(path) return filepath.Clean(output)
} }
func init() { func init() {

View file

@ -50,31 +50,31 @@ Exports the config to an image file ~/mytheme.png.
Exports the config to an image file using customized output options.`, Exports the config to an image file using customized output options.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{ cfg := config.Load(configFlag, shell.GENERIC, false)
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Config: configFlag, Config: configFlag,
Shell: shell.GENERIC, Shell: shell.GENERIC,
TerminalWidth: 150, TerminalWidth: 150,
},
} }
env.Init() env := &runtime.Terminal{}
defer env.Close() env.Init(flags)
template.Init(env) template.Init(env, cfg.Var)
cfg := config.Load(env) defer func() {
template.SaveCache()
env.Close()
}()
// set sane defaults for things we don't print // set sane defaults for things we don't print
cfg.ConsoleTitleTemplate = "" cfg.ConsoleTitleTemplate = ""
cfg.PWD = "" cfg.PWD = ""
// add variables to the environment
env.Var = cfg.Var
terminal.Init(shell.GENERIC) terminal.Init(shell.GENERIC)
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors() terminal.Colors = cfg.MakeColors(env)
eng := &prompt.Engine{ eng := &prompt.Engine{
Config: cfg, Config: cfg,
@ -90,7 +90,7 @@ Exports the config to an image file using customized output options.`,
} }
if outputImage != "" { if outputImage != "" {
imageCreator.Path = cleanOutputPath(outputImage, env) imageCreator.Path = cleanOutputPath(outputImage)
} }
err := imageCreator.Init(env) err := imageCreator.Init(env)

View file

@ -5,6 +5,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -39,15 +40,17 @@ Migrates the ~/myconfig.omp.json config file to TOML and writes the result to yo
A backup of the current config can be found at ~/myconfig.omp.json.bak.`, A backup of the current config can be found at ~/myconfig.omp.json.bak.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{ cfg := config.Load(configFlag, shell.GENERIC, true)
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Config: configFlag, Config: configFlag,
Migrate: true, Migrate: true,
},
} }
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
cfg := config.Load(env)
if write { if write {
cfg.BackupAndMigrate() cfg.BackupAndMigrate()
return return

View file

@ -5,6 +5,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -34,15 +35,15 @@ Migrates the ~/myconfig.omp.json config file's glyphs and writes the result to y
A backup of the current config can be found at ~/myconfig.omp.json.bak.`, A backup of the current config can be found at ~/myconfig.omp.json.bak.`,
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{ cfg := config.Load(configFlag, shell.GENERIC, false)
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Config: configFlag, Config: configFlag,
},
} }
env.Init() env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
cfg := config.Load(env)
cfg.MigrateGlyphs = true cfg.MigrateGlyphs = true
if len(format) == 0 { if len(format) == 0 {

View file

@ -36,29 +36,29 @@ func createDebugCmd() *cobra.Command {
return return
} }
env := &runtime.Terminal{ cfg := config.Load(configFlag, args[0], false)
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Config: configFlag, Config: configFlag,
Debug: true, Debug: true,
PWD: pwd, PWD: pwd,
Shell: args[0], Shell: args[0],
Plain: plain, Plain: plain,
},
} }
env.Init() env := &runtime.Terminal{}
defer env.Close() env.Init(flags)
template.Init(env) template.Init(env, cfg.Var)
cfg := config.Load(env) defer func() {
template.SaveCache()
// add variables to the environment env.Close()
env.Var = cfg.Var }()
terminal.Init(args[0]) terminal.Init(args[0])
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors() terminal.Colors = cfg.MakeColors(env)
terminal.Plain = plain terminal.Plain = plain
eng := &prompt.Engine{ eng := &prompt.Engine{

View file

@ -43,14 +43,13 @@ func init() {
} }
func toggleFeature(cmd *cobra.Command, feature string, enable bool) { func toggleFeature(cmd *cobra.Command, feature string, enable bool) {
env := &runtime.Terminal{ flags := &runtime.Flags{
CmdFlags: &runtime.Flags{
Shell: shellName, Shell: shellName,
SaveCache: true, SaveCache: true,
},
} }
env.Init() env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
if len(feature) == 0 { if len(feature) == 0 {

View file

@ -36,13 +36,12 @@ This command is used to install fonts and configure the font in your terminal.
fontName = args[1] fontName = args[1]
} }
env := &runtime.Terminal{ flags := &runtime.Flags{
CmdFlags: &runtime.Flags{
SaveCache: true, SaveCache: true,
},
} }
env.Init() env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
terminal.Init(env.Shell()) terminal.Init(env.Shell())

View file

@ -45,12 +45,12 @@ This command is used to get the value of the following variables:
return return
} }
env := &runtime.Terminal{ flags := &runtime.Flags{
CmdFlags: &runtime.Flags{
Shell: shellName, Shell: shellName,
},
} }
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
switch args[0] { switch args[0] {

View file

@ -70,23 +70,26 @@ func runInit(sh string) {
startTime = time.Now() startTime = time.Now()
} }
env := &runtime.Terminal{ cfg := config.Load(configFlag, sh, false)
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Shell: sh, Shell: sh,
Config: configFlag, Config: configFlag,
Strict: strict, Strict: strict,
Debug: debug, Debug: debug,
},
} }
env.Init() env := &runtime.Terminal{}
defer env.Close() env.Init(flags)
template.Init(env) template.Init(env, cfg.Var)
cfg := config.Load(env) defer func() {
template.SaveCache()
env.Close()
}()
feats := cfg.Features() feats := cfg.Features(env)
var output string var output string

View file

@ -15,13 +15,12 @@ var noticeCmd = &cobra.Command{
Long: "Print the upgrade notice when a new version is available.", Long: "Print the upgrade notice when a new version is available.",
Args: cobra.NoArgs, Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{ flags := &runtime.Flags{
CmdFlags: &runtime.Flags{
SaveCache: true, SaveCache: true,
},
} }
env.Init() env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
if notice, hasNotice := upgrade.Notice(env, false); hasNotice { if notice, hasNotice := upgrade.Notice(env, false); hasNotice {

View file

@ -5,6 +5,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/prompt" "github.com/jandedobbeleer/oh-my-posh/src/prompt"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -81,7 +82,11 @@ func createPrintCmd() *cobra.Command {
} }
eng := prompt.New(flags) eng := prompt.New(flags)
defer eng.Env.Close()
defer func() {
template.SaveCache()
eng.Env.Close()
}()
switch args[0] { switch args[0] {
case prompt.DEBUG: case prompt.DEBUG:

View file

@ -20,13 +20,12 @@ var toggleCmd = &cobra.Command{
return return
} }
env := &runtime.Terminal{ flags := &runtime.Flags{
CmdFlags: &runtime.Flags{
SaveCache: true, SaveCache: true,
},
} }
env.Init() env := &runtime.Terminal{}
env.Init(flags)
defer env.Close() defer env.Close()
togglesCache, _ := env.Session().Get(cache.TOGGLECACHE) togglesCache, _ := env.Session().Get(cache.TOGGLECACHE)

View file

@ -33,7 +33,7 @@ var upgradeCmd = &cobra.Command{
} }
env := &runtime.Terminal{} env := &runtime.Terminal{}
env.Init() env.Init(nil)
defer env.Close() defer env.Close()
terminal.Init(env.Shell()) terminal.Init(env.Shell())

View file

@ -81,13 +81,11 @@ func TestAnsiRender(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(mock.Environment) env := new(mock.Environment)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{})
env.On("Getenv", "TERM_PROGRAM").Return(tc.Term) env.On("Getenv", "TERM_PROGRAM").Return(tc.Term)
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
template.Init(env) template.Cache = new(cache.Template)
template.Init(env, nil)
ansi := Ansi("{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}") ansi := Ansi("{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
got := ansi.ResolveTemplate() got := ansi.ResolveTemplate()

View file

@ -22,7 +22,6 @@ const (
// Config holds all the theme for rendering the prompt // Config holds all the theme for rendering the prompt
type Config struct { type Config struct {
env runtime.Environment
Palette color.Palette `json:"palette,omitempty" toml:"palette,omitempty"` Palette color.Palette `json:"palette,omitempty" toml:"palette,omitempty"`
DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"` DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,omitempty"`
Var map[string]any `json:"var,omitempty" toml:"var,omitempty"` Var map[string]any `json:"var,omitempty" toml:"var,omitempty"`
@ -53,9 +52,9 @@ type Config struct {
FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"` FinalSpace bool `json:"final_space,omitempty" toml:"final_space,omitempty"`
} }
func (cfg *Config) MakeColors() color.String { func (cfg *Config) MakeColors(env runtime.Environment) color.String {
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1" cacheDisabled := env.Getenv("OMP_CACHE_DISABLED") == "1"
return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env) return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, env)
} }
func (cfg *Config) getPalette() color.Palette { func (cfg *Config) getPalette() color.Palette {
@ -88,7 +87,7 @@ func (cfg *Config) getPalette() color.Palette {
return palette return palette
} }
func (cfg *Config) Features() shell.Features { func (cfg *Config) Features(env runtime.Environment) shell.Features {
var feats shell.Features var feats shell.Features
if cfg.TransientPrompt != nil { if cfg.TransientPrompt != nil {
@ -100,12 +99,12 @@ func (cfg *Config) Features() shell.Features {
} }
autoUpgrade := cfg.AutoUpgrade autoUpgrade := cfg.AutoUpgrade
if _, OK := cfg.env.Cache().Get(AUTOUPGRADE); OK { if _, OK := env.Cache().Get(AUTOUPGRADE); OK {
autoUpgrade = true autoUpgrade = true
} }
upgradeNotice := cfg.UpgradeNotice upgradeNotice := cfg.UpgradeNotice
if _, OK := cfg.env.Cache().Get(UPGRADENOTICE); OK { if _, OK := env.Cache().Get(UPGRADENOTICE); OK {
upgradeNotice = true upgradeNotice = true
} }
@ -125,7 +124,7 @@ func (cfg *Config) Features() shell.Features {
feats = append(feats, shell.Tooltips) feats = append(feats, shell.Tooltips)
} }
if cfg.env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) { if env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) {
feats = append(feats, shell.PromptMark) feats = append(feats, shell.PromptMark)
} }

View file

@ -9,7 +9,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestGetPalette(t *testing.T) { func TestGetPalette(t *testing.T) {
@ -92,17 +91,14 @@ func TestGetPalette(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := &mock.Environment{} env := &mock.Environment{}
env.On("TemplateCache").Return(&cache.Template{
Shell: "bash",
})
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Shell").Return("bash") env.On("Shell").Return("bash")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
template.Init(env) template.Cache = &cache.Template{
Shell: "bash",
}
template.Init(env, nil)
cfg := &Config{ cfg := &Config{
env: env,
Palette: tc.Palette, Palette: tc.Palette,
Palettes: tc.Palettes, Palettes: tc.Palettes,
} }

View file

@ -3,11 +3,10 @@ package config
import ( import (
"github.com/jandedobbeleer/oh-my-posh/src/color" "github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/segments" "github.com/jandedobbeleer/oh-my-posh/src/segments"
) )
func Default(env runtime.Environment, warning bool) *Config { func Default(warning bool) *Config {
exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}" exitBackgroundTemplate := "{{ if gt .Code 0 }}p:red{{ end }}"
exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} " exitTemplate := " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} "
@ -197,6 +196,5 @@ func Default(env runtime.Environment, warning bool) *Config {
}, },
} }
cfg.env = env
return cfg return cfg
} }

View file

@ -3,13 +3,17 @@ package config
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os"
stdOS "os" stdOS "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"time" "time"
"github.com/gookit/goutil/jsonutil" "github.com/gookit/goutil/jsonutil"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/shell"
json "github.com/goccy/go-json" json "github.com/goccy/go-json"
@ -18,11 +22,15 @@ import (
) )
// LoadConfig returns the default configuration including possible user overrides // LoadConfig returns the default configuration including possible user overrides
func Load(env runtime.Environment) *Config { func Load(configFile, sh string, migrate bool) *Config {
cfg := loadConfig(env) defer log.Trace(time.Now())
configFile = Path(configFile)
cfg := loadConfig(configFile)
// only migrate automatically when the switch isn't set // only migrate automatically when the switch isn't set
if !env.Flags().Migrate && cfg.Version < Version { if !migrate && cfg.Version < Version {
cfg.BackupAndMigrate() cfg.BackupAndMigrate()
} }
@ -39,7 +47,7 @@ func Load(env runtime.Environment) *Config {
// elv - broken OSC sequences // elv - broken OSC sequences
// xonsh - broken OSC sequences // xonsh - broken OSC sequences
// tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future // tcsh - overall broken, FTCS_COMMAND_EXECUTED could be added to POSH_POSTCMD in the future
switch env.Shell() { switch sh {
case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU: case shell.ELVISH, shell.XONSH, shell.TCSH, shell.NU:
cfg.ShellIntegration = false cfg.ShellIntegration = false
} }
@ -47,24 +55,71 @@ func Load(env runtime.Environment) *Config {
return cfg return cfg
} }
func loadConfig(env runtime.Environment) *Config { func Path(config string) string {
defer env.Trace(time.Now()) defer log.Trace(time.Now())
configFile := env.Flags().Config
// if the config flag is set, we'll use that over POSH_THEME
// in our internal shell logic, we'll always use the POSH_THEME
// due to not using --config to set the configuration
hasConfig := len(config) > 0
if poshTheme := os.Getenv("POSH_THEME"); len(poshTheme) > 0 && !hasConfig {
log.Debug("config set using POSH_THEME: %s", poshTheme)
return poshTheme
}
if len(config) == 0 {
return ""
}
if strings.HasPrefix(config, "https://") {
filePath, err := Download(cache.Path(), config)
if err != nil {
log.Error(err)
return ""
}
return filePath
}
isCygwin := func() bool {
return runtime.GOOS == "windows" && len(os.Getenv("OSTYPE")) > 0
}
// Cygwin path always needs the full path as we're on Windows but not really.
// Doing filepath actions will convert it to a Windows path and break the init script.
if isCygwin() {
log.Debug("cygwin detected, using full path for config")
return config
}
configFile := path.ReplaceTildePrefixWithHomeDir(config)
abs, err := filepath.Abs(configFile)
if err != nil {
log.Error(err)
return filepath.Clean(configFile)
}
return abs
}
func loadConfig(configFile string) *Config {
defer log.Trace(time.Now())
if len(configFile) == 0 { if len(configFile) == 0 {
env.Debug("no config file specified, using default") log.Debug("no config file specified, using default")
return Default(env, false) return Default(false)
} }
var cfg Config var cfg Config
cfg.origin = configFile cfg.origin = configFile
cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".") cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".")
cfg.env = env
data, err := stdOS.ReadFile(configFile) data, err := stdOS.ReadFile(configFile)
if err != nil { if err != nil {
env.Error(err) log.Error(err)
return Default(env, true) return Default(true)
} }
switch cfg.Format { switch cfg.Format {
@ -87,8 +142,8 @@ func loadConfig(env runtime.Environment) *Config {
} }
if err != nil { if err != nil {
env.Error(err) log.Error(err)
return Default(env, true) return Default(true)
} }
return &cfg return &cfg

View file

@ -121,7 +121,7 @@ func (segment *Segment) Execute(env runtime.Environment) {
if segment.writer.Enabled() { if segment.writer.Enabled() {
segment.Enabled = true segment.Enabled = true
env.TemplateCache().AddSegmentData(segment.Name(), segment.writer) template.Cache.AddSegmentData(segment.Name(), segment.writer)
} }
} }
@ -134,7 +134,7 @@ func (segment *Segment) Render() {
segment.Enabled = len(strings.ReplaceAll(text, " ", "")) > 0 segment.Enabled = len(strings.ReplaceAll(text, " ", "")) > 0
if !segment.Enabled { if !segment.Enabled {
segment.env.TemplateCache().RemoveSegmentData(segment.Name()) template.Cache.RemoveSegmentData(segment.Name())
return return
} }
@ -142,7 +142,7 @@ func (segment *Segment) Render() {
segment.setCache() segment.setCache()
// We do this to make `.Text` available for a cross-segment reference in an extra prompt. // We do this to make `.Text` available for a cross-segment reference in an extra prompt.
segment.env.TemplateCache().AddSegmentData(segment.Name(), segment.writer) template.Cache.AddSegmentData(segment.Name(), segment.writer)
} }
func (segment *Segment) Text() string { func (segment *Segment) Text() string {
@ -231,7 +231,7 @@ func (segment *Segment) restoreCache() bool {
} }
segment.Enabled = true segment.Enabled = true
segment.env.TemplateCache().AddSegmentData(segment.Name(), segment.writer) template.Cache.AddSegmentData(segment.Name(), segment.writer)
return true return true
} }

View file

@ -35,6 +35,7 @@ import (
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
fontCLI "github.com/jandedobbeleer/oh-my-posh/src/font" fontCLI "github.com/jandedobbeleer/oh-my-posh/src/font"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
@ -218,7 +219,7 @@ func (ir *Renderer) setOutputPath(config string) {
func (ir *Renderer) loadFonts() error { func (ir *Renderer) loadFonts() error {
var data []byte var data []byte
fontCachePath := filepath.Join(ir.env.CachePath(), "Hack.zip") fontCachePath := filepath.Join(cache.Path(), "Hack.zip")
if _, err := stdOS.Stat(fontCachePath); err == nil { if _, err := stdOS.Stat(fontCachePath); err == nil {
data, _ = stdOS.ReadFile(fontCachePath) data, _ = stdOS.ReadFile(fontCachePath)
} }

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/log"
) )
@ -66,7 +67,7 @@ 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("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())) e.write(fmt.Sprintf("\n%s %s\n", log.Text("Cache path:").Green().Bold().Plain(), cache.Path()))
cfg := e.Env.Flags().Config cfg := e.Env.Flags().Config
if len(cfg) == 0 { if len(cfg) == 0 {

View file

@ -462,19 +462,12 @@ func (e *Engine) rectifyTerminalWidth(diff int) {
// given configuration options, and is ready to print any // given configuration options, and is ready to print any
// of the prompt components. // of the prompt components.
func New(flags *runtime.Flags) *Engine { func New(flags *runtime.Flags) *Engine {
env := &runtime.Terminal{ cfg := config.Load(flags.Config, flags.Shell, flags.Migrate)
CmdFlags: flags,
}
env.Init() env := &runtime.Terminal{}
cfg := config.Load(env) env.Init(flags)
env.Var = cfg.Var
// To prevent cross-segment template referencing issues, this should not be moved elsewhere. template.Init(env, cfg.Var)
// Related: https://github.com/JanDeDobbeleer/oh-my-posh/discussions/2885#discussioncomment-4497439
env.PopulateTemplateCache()
template.Init(env)
flags.HasExtra = cfg.DebugPrompt != nil || flags.HasExtra = cfg.DebugPrompt != nil ||
cfg.SecondaryPrompt != nil || cfg.SecondaryPrompt != nil ||
@ -484,7 +477,7 @@ func New(flags *runtime.Flags) *Engine {
terminal.Init(env.Shell()) terminal.Init(env.Shell())
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors() terminal.Colors = cfg.MakeColors(env)
terminal.Plain = flags.Plain terminal.Plain = flags.Plain
eng := &Engine{ eng := &Engine{

View file

@ -6,6 +6,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/config" "github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/shell"
@ -13,7 +14,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/terminal" "github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestCanWriteRPrompt(t *testing.T) { func TestCanWriteRPrompt(t *testing.T) {
@ -64,7 +64,7 @@ func TestPrintPWD(t *testing.T) {
{Case: "OSC7", Config: terminal.OSC7, Expected: "\x1b]7;file://host/pwd\x1b\\"}, {Case: "OSC7", Config: terminal.OSC7, Expected: "\x1b]7;file://host/pwd\x1b\\"},
{Case: "OSC51", Config: terminal.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"}, {Case: "OSC51", Config: terminal.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"},
{Case: "Template (empty)", Config: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"}, {Case: "Template (empty)", Config: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"},
{Case: "Template (non empty)", Config: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;file://host/pwd\x1b\\"}, {Case: "Template (non empty)", Shell: shell.GENERIC, Config: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;file://host/pwd\x1b\\"},
{ {
Case: "OSC99 Cygwin", Case: "OSC99 Cygwin",
Pwd: `C:\Users\user\Documents\GitHub\oh-my-posh`, Pwd: `C:\Users\user\Documents\GitHub\oh-my-posh`,
@ -89,16 +89,16 @@ func TestPrintPWD(t *testing.T) {
env.On("Pwd").Return(tc.Pwd) env.On("Pwd").Return(tc.Pwd)
env.On("User").Return("user") env.On("User").Return("user")
env.On("Shell").Return(tc.Shell) env.On("Shell").Return(tc.Shell)
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
env.On("IsCygwin").Return(tc.Cygwin) env.On("IsCygwin").Return(tc.Cygwin)
env.On("Host").Return("host", nil) env.On("Host").Return("host", nil)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{ template.Cache = &cache.Template{
Shell: "shell", Shell: tc.Shell,
}) Segments: maps.NewConcurrent(),
}
template.Init(env, nil)
terminal.Init(shell.GENERIC) terminal.Init(shell.GENERIC)
template.Init(env)
engine := &Engine{ engine := &Engine{
Env: env, Env: env,
@ -121,15 +121,21 @@ func BenchmarkEngineRender(b *testing.B) {
} }
func engineRender() { func engineRender() {
cfg := config.Load("", shell.GENERIC, false)
env := &runtime.Terminal{} env := &runtime.Terminal{}
env.Init() env.Init(nil)
defer env.Close() defer env.Close()
cfg := config.Load(env) template.Cache = &cache.Template{
Segments: maps.NewConcurrent(),
}
template.Init(env, nil)
terminal.Init(shell.GENERIC) terminal.Init(shell.GENERIC)
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors() terminal.Colors = cfg.MakeColors(env)
engine := &Engine{ engine := &Engine{
Config: cfg, Config: cfg,
@ -139,12 +145,6 @@ func engineRender() {
engine.Primary() engine.Primary()
} }
func BenchmarkEngineRenderPalette(b *testing.B) {
for i := 0; i < b.N; i++ {
engineRender()
}
}
func TestGetTitle(t *testing.T) { func TestGetTitle(t *testing.T) {
cases := []struct { cases := []struct {
Template string Template string
@ -185,21 +185,21 @@ func TestGetTitle(t *testing.T) {
env.On("Pwd").Return(tc.Cwd) env.On("Pwd").Return(tc.Cwd)
env.On("Home").Return("/usr/home") env.On("Home").Return("/usr/home")
env.On("PathSeparator").Return(tc.PathSeparator) env.On("PathSeparator").Return(tc.PathSeparator)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil) env.On("Getenv", "USERDOMAIN").Return("MyCompany")
env.On("TemplateCache").Return(&cache.Template{ env.On("Shell").Return(tc.ShellName)
terminal.Init(shell.GENERIC)
template.Cache = &cache.Template{
Shell: tc.ShellName, Shell: tc.ShellName,
UserName: "MyUser", UserName: "MyUser",
Root: tc.Root, Root: tc.Root,
HostName: "MyHost", HostName: "MyHost",
PWD: tc.Cwd, PWD: tc.Cwd,
Folder: "vagrant", Folder: "vagrant",
}) Segments: maps.NewConcurrent(),
env.On("Getenv", "USERDOMAIN").Return("MyCompany") }
env.On("Shell").Return(tc.ShellName) template.Init(env, nil)
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
terminal.Init(shell.GENERIC)
template.Init(env)
engine := &Engine{ engine := &Engine{
Config: &config.Config{ Config: &config.Config{
@ -249,19 +249,19 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) {
env := new(mock.Environment) env := new(mock.Environment)
env.On("Pwd").Return(tc.Cwd) env.On("Pwd").Return(tc.Cwd)
env.On("Home").Return("/usr/home") env.On("Home").Return("/usr/home")
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil) env.On("Getenv", "USERDOMAIN").Return("MyCompany")
env.On("TemplateCache").Return(&cache.Template{ env.On("Shell").Return(tc.ShellName)
terminal.Init(shell.GENERIC)
template.Cache = &cache.Template{
Shell: tc.ShellName, Shell: tc.ShellName,
UserName: "MyUser", UserName: "MyUser",
Root: tc.Root, Root: tc.Root,
HostName: "", HostName: "",
}) Segments: maps.NewConcurrent(),
env.On("Getenv", "USERDOMAIN").Return("MyCompany") }
env.On("Shell").Return(tc.ShellName) template.Init(env, nil)
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
terminal.Init(shell.GENERIC)
template.Init(env)
engine := &Engine{ engine := &Engine{
Config: &config.Config{ Config: &config.Config{

View file

@ -33,42 +33,39 @@ type Environment interface {
Shell() string Shell() string
Platform() string Platform() string
StatusCodes() (int, string) StatusCodes() (int, string)
PathSeparator() string
HasFiles(pattern string) bool HasFiles(pattern string) bool
HasFilesInDir(dir, pattern string) bool HasFilesInDir(dir, pattern string) bool
HasFolder(folder string) bool HasFolder(folder string) bool
HasParentFilePath(path string, followSymlinks bool) (fileInfo *FileInfo, err error) HasParentFilePath(input string, followSymlinks bool) (fileInfo *FileInfo, err error)
HasFileInParentDirs(pattern string, depth uint) bool HasFileInParentDirs(pattern string, depth uint) bool
ResolveSymlink(path string) (string, error) ResolveSymlink(input string) (string, error)
DirMatchesOneOf(dir string, regexes []string) bool DirMatchesOneOf(dir string, regexes []string) bool
DirIsWritable(path string) bool DirIsWritable(input string) bool
CommandPath(command string) string CommandPath(command string) string
HasCommand(command string) bool HasCommand(command string) bool
FileContent(file string) string FileContent(file string) string
LsDir(path string) []fs.DirEntry LsDir(input string) []fs.DirEntry
RunCommand(command string, args ...string) (string, error) RunCommand(command string, args ...string) (string, error)
RunShellCommand(shell, command string) string RunShellCommand(shell, command string) string
ExecutionTime() float64 ExecutionTime() float64
Flags() *Flags Flags() *Flags
BatteryState() (*battery.Info, error) BatteryState() (*battery.Info, error)
QueryWindowTitles(processName, windowTitleRegex string) (string, error) QueryWindowTitles(processName, windowTitleRegex string) (string, error)
WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) WindowsRegistryKeyValue(key string) (*WindowsRegistryValue, error)
HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...http.RequestModifier) ([]byte, error) HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...http.RequestModifier) ([]byte, error)
IsWsl() bool IsWsl() bool
IsWsl2() bool IsWsl2() bool
IsCygwin() bool IsCygwin() bool
StackCount() int StackCount() int
TerminalWidth() (int, error) TerminalWidth() (int, error)
CachePath() string
Cache() cache.Cache Cache() cache.Cache
Session() cache.Cache Session() cache.Cache
Close() Close()
Logs() string Logs() string
InWSLSharedDrive() bool InWSLSharedDrive() bool
ConvertToLinuxPath(path string) string ConvertToLinuxPath(input string) string
ConvertToWindowsPath(path string) string ConvertToWindowsPath(input string) string
Connection(connectionType ConnectionType) (*Connection, error) Connection(connectionType ConnectionType) (*Connection, error)
TemplateCache() *cache.Template
CursorPosition() (row, col int) CursorPosition() (row, col int)
SystemInfo() (*SystemInfo, error) SystemInfo() (*SystemInfo, error)
Debug(message string) Debug(message string)
@ -84,6 +81,7 @@ type Flags struct {
Shell string Shell string
ShellVersion string ShellVersion string
PWD string PWD string
AbsolutePWD string
Type string Type string
ErrorCode int ErrorCode int
PromptCount int PromptCount int

View file

@ -47,8 +47,8 @@ func (env *Environment) HasFolder(folder string) bool {
return args.Bool(0) return args.Bool(0)
} }
func (env *Environment) ResolveSymlink(path string) (string, error) { func (env *Environment) ResolveSymlink(input string) (string, error) {
args := env.Called(path) args := env.Called(input)
return args.String(0), args.Error(1) return args.String(0), args.Error(1)
} }
@ -57,16 +57,11 @@ func (env *Environment) FileContent(file string) string {
return args.String(0) return args.String(0)
} }
func (env *Environment) LsDir(path string) []fs.DirEntry { func (env *Environment) LsDir(input string) []fs.DirEntry {
args := env.Called(path) args := env.Called(input)
return args.Get(0).([]fs.DirEntry) return args.Get(0).([]fs.DirEntry)
} }
func (env *Environment) PathSeparator() string {
args := env.Called()
return args.String(0)
}
func (env *Environment) User() string { func (env *Environment) User() string {
args := env.Called() args := env.Called()
return args.String(0) return args.String(0)
@ -226,11 +221,6 @@ func (env *Environment) Connection(connectionType runtime.ConnectionType) (*runt
return args.Get(0).(*runtime.Connection), args.Error(1) return args.Get(0).(*runtime.Connection), args.Error(1)
} }
func (env *Environment) TemplateCache() *cache.Template {
args := env.Called()
return args.Get(0).(*cache.Template)
}
func (env *Environment) MockGitCommand(dir, returnValue string, args ...string) { func (env *Environment) MockGitCommand(dir, returnValue string, args ...string) {
args = append([]string{"-C", dir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...) args = append([]string{"-C", dir, "--no-optional-locks", "-c", "core.quotepath=false", "-c", "color.status=false"}, args...)
env.On("RunCommand", "git", args).Return(returnValue, nil) env.On("RunCommand", "git", args).Return(returnValue, nil)

123
src/runtime/path/clean.go Normal file
View file

@ -0,0 +1,123 @@
package path
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
)
// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path consists entirely of separators, Base returns a single separator.
func Base(input string) string {
volumeName := filepath.VolumeName(input)
// Strip trailing slashes.
for len(input) > 0 && IsSeparator(input[len(input)-1]) {
input = input[0 : len(input)-1]
}
if len(input) == 0 {
return Separator()
}
if volumeName == input {
return input
}
// Throw away volume name
input = input[len(filepath.VolumeName(input)):]
// Find the last element
i := len(input) - 1
for i >= 0 && !IsSeparator(input[i]) {
i--
}
if i >= 0 {
input = input[i+1:]
}
// If empty now, it had only slashes.
if len(input) == 0 {
return Separator()
}
return input
}
func Clean(input string) string {
if len(input) == 0 {
return input
}
cleaned := input
separator := Separator()
// The prefix can be empty for a relative path.
var prefix string
if IsSeparator(cleaned[0]) {
prefix = separator
}
if runtime.GOOS == windows {
// Normalize (forward) slashes to backslashes on Windows.
cleaned = strings.ReplaceAll(cleaned, "/", `\`)
// Clean the prefix for a UNC path, if any.
if regex.MatchString(`^\\{2}[^\\]+`, cleaned) {
cleaned = strings.TrimPrefix(cleaned, `\\.\UNC\`)
if len(cleaned) == 0 {
return cleaned
}
prefix = `\\`
}
// Always use an uppercase drive letter on Windows.
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper)
}
sb := new(strings.Builder)
sb.WriteString(prefix)
// Clean slashes.
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), cleaned)
n := len(matches) - 1
for i, m := range matches {
sb.WriteString(m["element"])
if i != n {
sb.WriteString(separator)
}
}
return sb.String()
}
func ReplaceHomeDirPrefixWithTilde(path string) string {
home := Home()
if !strings.HasPrefix(path, home) {
return path
}
rem := path[len(home):]
if len(rem) == 0 || IsSeparator(rem[0]) {
return "~" + rem
}
return path
}
func ReplaceTildePrefixWithHomeDir(path string) string {
if !strings.HasPrefix(path, "~") {
return path
}
rem := path[1:]
if len(rem) == 0 || IsSeparator(rem[0]) {
return Home() + rem
}
return path
}

27
src/runtime/path/home.go Normal file
View file

@ -0,0 +1,27 @@
package path
import (
"os"
"github.com/jandedobbeleer/oh-my-posh/src/log"
)
func Home() string {
home := os.Getenv("HOME")
defer func() {
log.Debug(home)
}()
if len(home) > 0 {
return home
}
// fallback to older implemenations on Windows
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if len(home) == 0 {
home = os.Getenv("USERPROFILE")
}
return home
}

View file

@ -0,0 +1,34 @@
package path
import (
"runtime"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
)
const (
windows = "windows"
)
func Separator() string {
defer log.Trace(time.Now())
if runtime.GOOS == windows {
return `\`
}
return "/"
}
func IsSeparator(c uint8) bool {
if c == '/' {
return true
}
if runtime.GOOS == windows && c == '\\' {
return true
}
return false
}

View file

@ -2,7 +2,6 @@ package runtime
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -22,8 +21,8 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/maps" "github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd" "github.com/jandedobbeleer/oh-my-posh/src/runtime/cmd"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/http" "github.com/jandedobbeleer/oh-my-posh/src/runtime/http"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
disk "github.com/shirou/gopsutil/v3/disk" disk "github.com/shirou/gopsutil/v3/disk"
load "github.com/shirou/gopsutil/v3/load" load "github.com/shirou/gopsutil/v3/load"
@ -32,20 +31,20 @@ import (
type Terminal struct { type Terminal struct {
CmdFlags *Flags CmdFlags *Flags
Var maps.Simple
cmdCache *cache.Command cmdCache *cache.Command
deviceCache *cache.File deviceCache *cache.File
sessionCache *cache.File sessionCache *cache.File
tmplCache *cache.Template
lsDirMap maps.Concurrent lsDirMap maps.Concurrent
cwd string cwd string
host string host string
networks []*Connection networks []*Connection
} }
func (term *Terminal) Init() { func (term *Terminal) Init(flags *Flags) {
defer term.Trace(time.Now()) defer term.Trace(time.Now())
term.CmdFlags = flags
if term.CmdFlags == nil { if term.CmdFlags == nil {
term.CmdFlags = &Flags{} term.CmdFlags = &Flags{}
} }
@ -61,9 +60,9 @@ func (term *Terminal) Init() {
} }
initCache := func(fileName string) *cache.File { initCache := func(fileName string) *cache.File {
cache := &cache.File{} fileCache := &cache.File{}
cache.Init(filepath.Join(term.CachePath(), fileName), term.CmdFlags.SaveCache) fileCache.Init(filepath.Join(cache.Path(), fileName), term.CmdFlags.SaveCache)
return cache return fileCache
} }
term.deviceCache = initCache(cache.FileName) term.deviceCache = initCache(cache.FileName)
@ -72,67 +71,9 @@ func (term *Terminal) Init() {
term.setPwd() term.setPwd()
term.ResolveConfigPath()
term.cmdCache = &cache.Command{ term.cmdCache = &cache.Command{
Commands: maps.NewConcurrent(), Commands: maps.NewConcurrent(),
} }
term.tmplCache = new(cache.Template)
}
func (term *Terminal) ResolveConfigPath() {
defer term.Trace(time.Now())
// if the config flag is set, we'll use that over POSH_THEME
// in our internal shell logic, we'll always use the POSH_THEME
// due to not using --config to set the configuration
hasConfigFlag := len(term.CmdFlags.Config) > 0
if poshTheme := term.Getenv("POSH_THEME"); len(poshTheme) > 0 && !hasConfigFlag {
term.DebugF("config set using POSH_THEME: %s", poshTheme)
term.CmdFlags.Config = poshTheme
return
}
if len(term.CmdFlags.Config) == 0 {
term.Debug("no config set, fallback to default config")
return
}
if strings.HasPrefix(term.CmdFlags.Config, "https://") {
filePath, err := config.Download(term.CachePath(), term.CmdFlags.Config)
if err != nil {
term.Error(err)
term.CmdFlags.Config = ""
return
}
term.CmdFlags.Config = filePath
return
}
isCygwin := func() bool {
return term.Platform() == WINDOWS && len(term.Getenv("OSTYPE")) > 0
}
// Cygwin path always needs the full path as we're on Windows but not really.
// Doing filepath actions will convert it to a Windows path and break the init script.
if isCygwin() {
term.Debug("cygwin detected, using full path for config")
return
}
configFile := ReplaceTildePrefixWithHomeDir(term, term.CmdFlags.Config)
abs, err := filepath.Abs(configFile)
if err != nil {
term.Error(err)
term.CmdFlags.Config = filepath.Clean(configFile)
return
}
term.CmdFlags.Config = abs
} }
func (term *Terminal) Trace(start time.Time, args ...string) { func (term *Terminal) Trace(start time.Time, args ...string) {
@ -179,7 +120,7 @@ func (term *Terminal) setPwd() {
} }
if term.CmdFlags != nil && term.CmdFlags.PWD != "" { if term.CmdFlags != nil && term.CmdFlags.PWD != "" {
term.cwd = CleanPath(term, term.CmdFlags.PWD) term.cwd = path.Clean(term.CmdFlags.PWD)
term.Debug(term.cwd) term.Debug(term.cwd)
return return
} }
@ -277,9 +218,9 @@ func (term *Terminal) HasFolder(folder string) bool {
return isDir return isDir
} }
func (term *Terminal) ResolveSymlink(path string) (string, error) { func (term *Terminal) ResolveSymlink(input string) (string, error) {
defer term.Trace(time.Now(), path) defer term.Trace(time.Now(), input)
link, err := filepath.EvalSymlinks(path) link, err := filepath.EvalSymlinks(input)
if err != nil { if err != nil {
term.Error(err) term.Error(err)
return "", err return "", err
@ -293,35 +234,32 @@ func (term *Terminal) FileContent(file string) string {
if !filepath.IsAbs(file) { if !filepath.IsAbs(file) {
file = filepath.Join(term.Pwd(), file) file = filepath.Join(term.Pwd(), file)
} }
content, err := os.ReadFile(file) content, err := os.ReadFile(file)
if err != nil { if err != nil {
term.Error(err) term.Error(err)
return "" return ""
} }
fileContent := string(content) fileContent := string(content)
term.Debug(fileContent) term.Debug(fileContent)
return fileContent return fileContent
} }
func (term *Terminal) LsDir(path string) []fs.DirEntry { func (term *Terminal) LsDir(input string) []fs.DirEntry {
defer term.Trace(time.Now(), path) defer term.Trace(time.Now(), input)
entries, err := os.ReadDir(path)
entries, err := os.ReadDir(input)
if err != nil { if err != nil {
term.Error(err) term.Error(err)
return nil return nil
} }
term.DebugF("%v", entries) term.DebugF("%v", entries)
return entries return entries
} }
func (term *Terminal) PathSeparator() string {
defer term.Trace(time.Now())
if term.GOOS() == WINDOWS {
return `\`
}
return "/"
}
func (term *Terminal) User() string { func (term *Terminal) User() string {
defer term.Trace(time.Now()) defer term.Trace(time.Now())
user := os.Getenv("USER") user := os.Getenv("USER")
@ -356,6 +294,10 @@ func (term *Terminal) GOOS() string {
return runtime.GOOS return runtime.GOOS
} }
func (term *Terminal) Home() string {
return path.Home()
}
func (term *Terminal) RunCommand(command string, args ...string) (string, error) { func (term *Terminal) RunCommand(command string, args ...string) (string, error) {
defer term.Trace(time.Now(), append([]string{command}, args...)...) defer term.Trace(time.Now(), append([]string{command}, args...)...)
@ -384,16 +326,16 @@ func (term *Terminal) RunShellCommand(shell, command string) string {
func (term *Terminal) CommandPath(command string) string { func (term *Terminal) CommandPath(command string) string {
defer term.Trace(time.Now(), command) defer term.Trace(time.Now(), command)
if path, ok := term.cmdCache.Get(command); ok { if cmdPath, ok := term.cmdCache.Get(command); ok {
term.Debug(path) term.Debug(cmdPath)
return path return cmdPath
} }
path, err := exec.LookPath(command) cmdPath, err := exec.LookPath(command)
if err == nil { if err == nil {
term.cmdCache.Set(command, path) term.cmdCache.Set(command, cmdPath)
term.Debug(path) term.Debug(cmdPath)
return path return cmdPath
} }
term.Error(err) term.Error(err)
@ -402,9 +344,11 @@ func (term *Terminal) CommandPath(command string) string {
func (term *Terminal) HasCommand(command string) bool { func (term *Terminal) HasCommand(command string) bool {
defer term.Trace(time.Now(), command) defer term.Trace(time.Now(), command)
if path := term.CommandPath(command); path != "" {
if cmdPath := term.CommandPath(command); cmdPath != "" {
return true return true
} }
return false return false
} }
@ -529,20 +473,20 @@ func (term *Terminal) HTTPRequest(targetURL string, body io.Reader, timeout int,
func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*FileInfo, error) { func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*FileInfo, error) {
defer term.Trace(time.Now(), parent) defer term.Trace(time.Now(), parent)
path := term.Pwd() pwd := term.Pwd()
if followSymlinks { if followSymlinks {
if actual, err := term.ResolveSymlink(path); err == nil { if actual, err := term.ResolveSymlink(pwd); err == nil {
path = actual pwd = actual
} }
} }
for { for {
fileSystem := os.DirFS(path) fileSystem := os.DirFS(pwd)
info, err := fs.Stat(fileSystem, parent) info, err := fs.Stat(fileSystem, parent)
if err == nil { if err == nil {
return &FileInfo{ return &FileInfo{
ParentFolder: path, ParentFolder: pwd,
Path: filepath.Join(path, parent), Path: filepath.Join(pwd, parent),
IsDir: info.IsDir(), IsDir: info.IsDir(),
}, nil }, nil
} }
@ -551,8 +495,8 @@ func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*Fi
return nil, err return nil, err
} }
if dir := filepath.Dir(path); dir != path { if dir := filepath.Dir(pwd); dir != pwd {
path = dir pwd = dir
continue continue
} }
@ -563,6 +507,7 @@ func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*Fi
func (term *Terminal) StackCount() int { func (term *Terminal) StackCount() int {
defer term.Trace(time.Now()) defer term.Trace(time.Now())
if term.CmdFlags.StackCount < 0 { if term.CmdFlags.StackCount < 0 {
return 0 return 0
} }
@ -578,26 +523,8 @@ func (term *Terminal) Session() cache.Cache {
return term.sessionCache return term.sessionCache
} }
func (term *Terminal) saveTemplateCache() {
// only store this when in a primary prompt
// and when we have any extra prompt in the config
canSave := term.CmdFlags.Type == PRIMARY && term.CmdFlags.HasExtra
if !canSave {
return
}
tmplCache := term.TemplateCache()
tmplCache.SegmentsCache = tmplCache.Segments.ToSimple()
templateCache, err := json.Marshal(tmplCache)
if err == nil {
term.sessionCache.Set(cache.TEMPLATECACHE, string(templateCache), cache.ONEDAY)
}
}
func (term *Terminal) Close() { func (term *Terminal) Close() {
defer term.Trace(time.Now()) defer term.Trace(time.Now())
term.saveTemplateCache()
term.clearCacheFiles() term.clearCacheFiles()
term.deviceCache.Close() term.deviceCache.Close()
term.sessionCache.Close() term.sessionCache.Close()
@ -608,7 +535,7 @@ func (term *Terminal) clearCacheFiles() {
return return
} }
deletedFiles, err := cache.Clear(term.CachePath(), false) deletedFiles, err := cache.Clear(cache.Path(), false)
if err != nil { if err != nil {
term.Error(err) term.Error(err)
return return
@ -619,88 +546,10 @@ func (term *Terminal) clearCacheFiles() {
} }
} }
func (term *Terminal) PopulateTemplateCache() {
if !term.CmdFlags.IsPrimary {
// Load the template cache for a non-primary prompt before rendering any templates.
term.loadTemplateCache()
return
}
tmplCache := term.tmplCache
tmplCache.Root = term.Root()
tmplCache.Shell = term.Shell()
tmplCache.ShellVersion = term.CmdFlags.ShellVersion
tmplCache.Code, _ = term.StatusCodes()
tmplCache.WSL = term.IsWsl()
tmplCache.Segments = maps.NewConcurrent()
tmplCache.PromptCount = term.CmdFlags.PromptCount
tmplCache.Var = make(map[string]any)
tmplCache.Jobs = term.CmdFlags.JobCount
if term.Var != nil {
tmplCache.Var = term.Var
}
pwd := term.Pwd()
tmplCache.PWD = ReplaceHomeDirPrefixWithTilde(term, pwd)
tmplCache.AbsolutePWD = pwd
if term.IsWsl() {
tmplCache.AbsolutePWD, _ = term.RunCommand("wslpath", "-m", pwd)
}
tmplCache.PSWD = term.CmdFlags.PSWD
tmplCache.Folder = Base(term, pwd)
if term.GOOS() == WINDOWS && strings.HasSuffix(tmplCache.Folder, ":") {
tmplCache.Folder += `\`
}
tmplCache.UserName = term.User()
if host, err := term.Host(); err == nil {
tmplCache.HostName = host
}
goos := term.GOOS()
tmplCache.OS = goos
if goos == LINUX {
tmplCache.OS = term.Platform()
}
val := term.Getenv("SHLVL")
if shlvl, err := strconv.Atoi(val); err == nil {
tmplCache.SHLVL = shlvl
}
}
func (term *Terminal) loadTemplateCache() {
defer term.Trace(time.Now())
val, OK := term.sessionCache.Get(cache.TEMPLATECACHE)
if !OK {
return
}
tmplCache := term.tmplCache
err := json.Unmarshal([]byte(val), &tmplCache)
if err != nil {
term.Error(err)
return
}
tmplCache.Segments = tmplCache.SegmentsCache.ToConcurrent()
}
func (term *Terminal) Logs() string { func (term *Terminal) Logs() string {
return log.String() return log.String()
} }
func (term *Terminal) TemplateCache() *cache.Template {
return term.tmplCache
}
func (term *Terminal) DirMatchesOneOf(dir string, regexes []string) (match bool) { func (term *Terminal) DirMatchesOneOf(dir string, regexes []string) (match bool) {
// 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
@ -797,170 +646,6 @@ func (term *Terminal) SystemInfo() (*SystemInfo, error) {
return s, nil return s, nil
} }
func (term *Terminal) CachePath() string {
defer term.Trace(time.Now())
returnOrBuildCachePath := func(path string) (string, bool) {
// validate root path
if _, err := os.Stat(path); err != nil {
return "", false
}
// validate oh-my-posh folder, if non existent, create it
cachePath := filepath.Join(path, "oh-my-posh")
if _, err := os.Stat(cachePath); err == nil {
return cachePath, true
}
if err := os.Mkdir(cachePath, 0o755); err != nil {
return "", false
}
return cachePath, true
}
// WINDOWS cache folder, should not exist elsewhere
if cachePath, OK := returnOrBuildCachePath(term.Getenv("LOCALAPPDATA")); OK {
return cachePath
}
// allow the user to set the cache path using OMP_CACHE_DIR
if cachePath, OK := returnOrBuildCachePath(term.Getenv("OMP_CACHE_DIR")); OK {
return cachePath
}
// get XDG_CACHE_HOME if present
if cachePath, OK := returnOrBuildCachePath(term.Getenv("XDG_CACHE_HOME")); OK {
return cachePath
}
// try to create the cache folder in the user's home directory if non-existent
dotCache := filepath.Join(term.Home(), ".cache")
if _, err := os.Stat(dotCache); err != nil {
_ = os.Mkdir(dotCache, 0o755)
}
// HOME cache folder
if cachePath, OK := returnOrBuildCachePath(dotCache); OK {
return cachePath
}
return term.Home()
}
func IsPathSeparator(env Environment, c uint8) bool {
if c == '/' {
return true
}
if env.GOOS() == WINDOWS && c == '\\' {
return true
}
return false
}
// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path consists entirely of separators, Base returns a single separator.
func Base(env Environment, path string) string {
volumeName := filepath.VolumeName(path)
// Strip trailing slashes.
for len(path) > 0 && IsPathSeparator(env, path[len(path)-1]) {
path = path[0 : len(path)-1]
}
if len(path) == 0 {
return env.PathSeparator()
}
if volumeName == path {
return path
}
// Throw away volume name
path = path[len(filepath.VolumeName(path)):]
// Find the last element
i := len(path) - 1
for i >= 0 && !IsPathSeparator(env, path[i]) {
i--
}
if i >= 0 {
path = path[i+1:]
}
// If empty now, it had only slashes.
if len(path) == 0 {
return env.PathSeparator()
}
return path
}
func CleanPath(env Environment, path string) string {
if len(path) == 0 {
return path
}
cleaned := path
separator := env.PathSeparator()
// The prefix can be empty for a relative path.
var prefix string
if IsPathSeparator(env, cleaned[0]) {
prefix = separator
}
if env.GOOS() == WINDOWS {
// Normalize (forward) slashes to backslashes on Windows.
cleaned = strings.ReplaceAll(cleaned, "/", `\`)
// Clean the prefix for a UNC path, if any.
if regex.MatchString(`^\\{2}[^\\]+`, cleaned) {
cleaned = strings.TrimPrefix(cleaned, `\\.\UNC\`)
if len(cleaned) == 0 {
return cleaned
}
prefix = `\\`
}
// Always use an uppercase drive letter on Windows.
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper)
}
sb := new(strings.Builder)
sb.WriteString(prefix)
// Clean slashes.
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), cleaned)
n := len(matches) - 1
for i, m := range matches {
sb.WriteString(m["element"])
if i != n {
sb.WriteString(separator)
}
}
return sb.String()
}
func ReplaceTildePrefixWithHomeDir(env Environment, path string) string {
if !strings.HasPrefix(path, "~") {
return path
}
rem := path[1:]
if len(rem) == 0 || IsPathSeparator(env, rem[0]) {
return env.Home() + rem
}
return path
}
func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string {
home := env.Home()
if !strings.HasPrefix(path, home) {
return path
}
rem := path[len(home):]
if len(rem) == 0 || IsPathSeparator(env, rem[0]) {
return "~" + rem
}
return path
}
func cleanHostName(hostName string) string { func cleanHostName(hostName string) string {
garbage := []string{ garbage := []string{
".lan", ".lan",

View file

@ -20,10 +20,6 @@ func (term *Terminal) Root() bool {
return os.Geteuid() == 0 return os.Geteuid() == 0
} }
func (term *Terminal) Home() string {
return os.Getenv("HOME")
}
func (term *Terminal) QueryWindowTitles(_, _ string) (string, error) { func (term *Terminal) QueryWindowTitles(_, _ string) (string, error) {
return "", &NotImplemented{} return "", &NotImplemented{}
} }
@ -132,24 +128,24 @@ func (term *Terminal) InWSLSharedDrive() bool {
return !strings.HasPrefix(windowsPath, `//wsl.localhost/`) && !strings.HasPrefix(windowsPath, `//wsl$/`) return !strings.HasPrefix(windowsPath, `//wsl.localhost/`) && !strings.HasPrefix(windowsPath, `//wsl$/`)
} }
func (term *Terminal) ConvertToWindowsPath(path string) string { func (term *Terminal) ConvertToWindowsPath(input string) string {
windowsPath, err := term.RunCommand("wslpath", "-m", path) windowsPath, err := term.RunCommand("wslpath", "-m", input)
if err == nil { if err == nil {
return windowsPath return windowsPath
} }
return path return input
} }
func (term *Terminal) ConvertToLinuxPath(path string) string { func (term *Terminal) ConvertToLinuxPath(input string) string {
if linuxPath, err := term.RunCommand("wslpath", "-u", path); err == nil { if linuxPath, err := term.RunCommand("wslpath", "-u", input); err == nil {
return linuxPath return linuxPath
} }
return path return input
} }
func (term *Terminal) DirIsWritable(path string) bool { func (term *Terminal) DirIsWritable(input string) bool {
defer term.Trace(time.Now(), path) defer term.Trace(time.Now(), input)
return unix.Access(path, unix.W_OK) == nil return unix.Access(input, unix.W_OK) == nil
} }
func (term *Terminal) Connection(_ ConnectionType) (*Connection, error) { func (term *Terminal) Connection(_ ConnectionType) (*Connection, error) {

View file

@ -3,12 +3,12 @@ package runtime
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/Azure/go-ansiterm/winterm" "github.com/Azure/go-ansiterm/winterm"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )
@ -50,22 +50,6 @@ func (term *Terminal) Root() bool {
return member return member
} }
func (term *Terminal) Home() string {
home := os.Getenv("HOME")
defer func() {
term.Debug(home)
}()
if len(home) > 0 {
return home
}
// fallback to older implemenations on Windows
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
return home
}
func (term *Terminal) QueryWindowTitles(processName, windowTitleRegex string) (string, error) { func (term *Terminal) QueryWindowTitles(processName, windowTitleRegex string) (string, error) {
defer term.Trace(time.Now(), windowTitleRegex) defer term.Trace(time.Now(), windowTitleRegex)
title, err := queryWindowTitles(processName, windowTitleRegex) title, err := queryWindowTitles(processName, windowTitleRegex)
@ -128,8 +112,8 @@ func (term *Terminal) Platform() string {
// If the path ends in "\", the "(Default)" key in that path is retrieved. // If the path ends in "\", the "(Default)" key in that path is retrieved.
// //
// Returns a variant type if successful; nil and an error if not. // Returns a variant type if successful; nil and an error if not.
func (term *Terminal) WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) { func (term *Terminal) WindowsRegistryKeyValue(input string) (*WindowsRegistryValue, error) {
term.Trace(time.Now(), path) term.Trace(time.Now(), input)
// Format: // Format:
// "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID" // "HKLM\Software\Microsoft\Windows NT\CurrentVersion\EditionID"
@ -143,16 +127,16 @@ func (term *Terminal) WindowsRegistryKeyValue(path string) (*WindowsRegistryValu
// //
// If 3 is "" (i.e. the path ends with "\"), then get (Default) key. // If 3 is "" (i.e. the path ends with "\"), then get (Default) key.
// //
rootKey, regPath, found := strings.Cut(path, `\`) rootKey, regPath, found := strings.Cut(input, `\`)
if !found { if !found {
err := fmt.Errorf("Error, malformed registry path: '%s'", path) err := fmt.Errorf("Error, malformed registry path: '%s'", input)
term.Error(err) term.Error(err)
return nil, err return nil, err
} }
var regKey string var regKey string
if !strings.HasSuffix(regPath, `\`) { if !strings.HasSuffix(regPath, `\`) {
regKey = Base(term, regPath) regKey = path.Base(regPath)
if len(regKey) != 0 { if len(regKey) != 0 {
regPath = strings.TrimSuffix(regPath, `\`+regKey) regPath = strings.TrimSuffix(regPath, `\`+regKey)
} }
@ -218,17 +202,17 @@ func (term *Terminal) InWSLSharedDrive() bool {
return false return false
} }
func (term *Terminal) ConvertToWindowsPath(path string) string { func (term *Terminal) ConvertToWindowsPath(input string) string {
return strings.ReplaceAll(path, `\`, "/") return strings.ReplaceAll(input, `\`, "/")
} }
func (term *Terminal) ConvertToLinuxPath(path string) string { func (term *Terminal) ConvertToLinuxPath(input string) string {
return path return input
} }
func (term *Terminal) DirIsWritable(path string) bool { func (term *Terminal) DirIsWritable(input string) bool {
defer term.Trace(time.Now()) defer term.Trace(time.Now())
return term.isWriteable(path) return term.isWriteable(input)
} }
func (term *Terminal) Connection(connectionType ConnectionType) (*Connection, error) { func (term *Terminal) Connection(connectionType ConnectionType) (*Connection, error) {

View file

@ -11,6 +11,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -365,8 +366,8 @@ func (g *Git) getBareRepoInfo() {
// we can still have a pointer to a bare repo // we can still have a pointer to a bare repo
if file, err := g.env.HasParentFilePath(".git", true); err == nil && !file.IsDir { if file, err := g.env.HasParentFilePath(".git", true); err == nil && !file.IsDir {
content := g.FileContents(file.ParentFolder, ".git") content := g.FileContents(file.ParentFolder, ".git")
path := strings.TrimPrefix(content, "gitdir: ") dir := strings.TrimPrefix(content, "gitdir: ")
g.workingDir = filepath.Join(file.ParentFolder, path) g.workingDir = filepath.Join(file.ParentFolder, dir)
} }
head := g.FileContents(g.workingDir, "HEAD") head := g.FileContents(g.workingDir, "HEAD")
@ -384,7 +385,7 @@ func (g *Git) getBareRepoInfo() {
} }
func (g *Git) setDir(dir string) { func (g *Git) setDir(dir string) {
dir = runtime.ReplaceHomeDirPrefixWithTilde(g.env, dir) // align with template PWD dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD
if g.env.GOOS() == runtime.WINDOWS { if g.env.GOOS() == runtime.WINDOWS {
g.Dir = strings.TrimSuffix(dir, `\.git`) g.Dir = strings.TrimSuffix(dir, `\.git`)
return return
@ -516,9 +517,9 @@ func (g *Git) cleanUpstreamURL(url string) string {
} }
if len(match) != 0 { if len(match) != 0 {
path := strings.Trim(match["PATH"], "/") repoPath := strings.Trim(match["PATH"], "/")
path = strings.TrimSuffix(path, ".git") repoPath = strings.TrimSuffix(repoPath, ".git")
return fmt.Sprintf("https://%s/%s", match["URL"], path) return fmt.Sprintf("https://%s/%s", match["URL"], repoPath)
} }
// codecommit::region-identifier-id://repo-name // codecommit::region-identifier-id://repo-name
@ -923,12 +924,12 @@ func (g *Git) getSwitchMode(property properties.Property, gitSwitch, mode string
func (g *Git) repoName() string { func (g *Git) repoName() string {
if !g.IsWorkTree { if !g.IsWorkTree {
return runtime.Base(g.env, g.convertToLinuxPath(g.realDir)) return path.Base(g.convertToLinuxPath(g.realDir))
} }
ind := strings.LastIndex(g.workingDir, ".git/worktrees") ind := strings.LastIndex(g.workingDir, ".git/worktrees")
if ind > -1 { if ind > -1 {
return runtime.Base(g.env, g.workingDir[:ind]) return path.Base(g.workingDir[:ind])
} }
return "" return ""

View file

@ -9,5 +9,6 @@ func resolveGitPath(base, path string) string {
if filepath.IsAbs(path) { if filepath.IsAbs(path) {
return path return path
} }
return filepath.Join(base, path) return filepath.Join(base, path)
} }

View file

@ -7,9 +7,11 @@ func resolveGitPath(base, path string) string {
if len(path) == 0 { if len(path) == 0 {
return base return base
} }
if filepath.IsAbs(path) { if filepath.IsAbs(path) {
return path return path
} }
// Note that git on Windows uses slashes exclusively. And it's okay // Note that git on Windows uses slashes exclusively. And it's okay
// because Windows actually accepts both directory separators. More // because Windows actually accepts both directory separators. More
// importantly, however, parts of the git segment depend on those // importantly, however, parts of the git segment depend on those
@ -18,5 +20,6 @@ func resolveGitPath(base, path string) string {
// path is a disk-relative path. // path is a disk-relative path.
return filepath.VolumeName(base) + path return filepath.VolumeName(base) + path
} }
return filepath.ToSlash(filepath.Join(base, path)) return filepath.ToSlash(filepath.Join(base, path))
} }

View file

@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
) )
const ( const (
@ -90,7 +91,7 @@ func (hg *Mercurial) shouldDisplay() bool {
} }
func (hg *Mercurial) setDir(dir string) { func (hg *Mercurial) setDir(dir string) {
dir = runtime.ReplaceHomeDirPrefixWithTilde(hg.env, dir) // align with template PWD dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD
if hg.env.GOOS() == runtime.WINDOWS { if hg.env.GOOS() == runtime.WINDOWS {
hg.Dir = strings.TrimSuffix(dir, `\.hg`) hg.Dir = strings.TrimSuffix(dir, `\.hg`)
return return

View file

@ -6,6 +6,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -89,9 +90,6 @@ func TestOSInfo(t *testing.T) {
env := new(mock.Environment) env := new(mock.Environment)
env.On("GOOS").Return(tc.GOOS) env.On("GOOS").Return(tc.GOOS)
env.On("Platform").Return(tc.Platform) env.On("Platform").Return(tc.Platform)
env.On("TemplateCache").Return(&cache.Template{
WSL: tc.IsWSL,
})
props := properties.Map{ props := properties.Map{
DisplayDistroName: tc.DisplayDistroName, DisplayDistroName: tc.DisplayDistroName,
@ -106,6 +104,10 @@ func TestOSInfo(t *testing.T) {
osInfo := &Os{} osInfo := &Os{}
osInfo.Init(props, env) osInfo.Init(props, env)
template.Cache = &cache.Template{
WSL: tc.IsWSL,
}
_ = osInfo.Enabled() _ = osInfo.Enabled()
assert.Equal(t, tc.ExpectedString, renderTemplate(env, osInfo.Template(), osInfo), tc.Case) assert.Equal(t, tc.ExpectedString, renderTemplate(env, osInfo.Template(), osInfo), tc.Case)
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/template"
) )
@ -126,7 +127,7 @@ func (pt *Path) Enabled() bool {
pt.setStyle() pt.setStyle()
pwd := pt.env.Pwd() pwd := pt.env.Pwd()
pt.Location = pt.env.TemplateCache().AbsolutePWD pt.Location = pt.env.Flags().AbsolutePWD
if pt.env.GOOS() == runtime.WINDOWS { if pt.env.GOOS() == runtime.WINDOWS {
pt.Location = strings.ReplaceAll(pt.Location, `\`, `/`) pt.Location = strings.ReplaceAll(pt.Location, `\`, `/`)
} }
@ -152,7 +153,7 @@ func (pt *Path) setPaths() {
pt.cygPath = displayCygpath() pt.cygPath = displayCygpath()
pt.windowsPath = pt.env.GOOS() == runtime.WINDOWS && !pt.cygPath pt.windowsPath = pt.env.GOOS() == runtime.WINDOWS && !pt.cygPath
pt.pathSeparator = pt.env.PathSeparator() pt.pathSeparator = path.Separator()
pt.pwd = pt.env.Pwd() pt.pwd = pt.env.Pwd()
if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 { if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 {
@ -186,10 +187,12 @@ func (pt *Path) Parent() string {
if !pt.endWithSeparator(pt.root) { if !pt.endWithSeparator(pt.root) {
sb.WriteString(folderSeparator) sb.WriteString(folderSeparator)
} }
for _, folder := range folders[:len(folders)-1] { for _, folder := range folders[:len(folders)-1] {
sb.WriteString(folder) sb.WriteString(folder)
sb.WriteString(folderSeparator) sb.WriteString(folderSeparator)
} }
return sb.String() return sb.String()
} }
@ -267,6 +270,7 @@ func (pt *Path) getFolderSeparator() string {
if len(separator) == 0 { if len(separator) == 0 {
return pt.pathSeparator return pt.pathSeparator
} }
return separator return separator
} }
@ -568,21 +572,21 @@ func (pt *Path) setMappedLocations() {
Context: pt, Context: pt,
} }
path, err := tmpl.Render() location, err := tmpl.Render()
if err != nil { if err != nil {
pt.env.Error(err) pt.env.Error(err)
} }
if len(path) == 0 { if len(location) == 0 {
continue continue
} }
// When two templates resolve to the same key, the values are compared in ascending order and the latter is taken. // When two templates resolve to the same key, the values are compared in ascending order and the latter is taken.
if v, exist := mappedLocations[pt.normalize(path)]; exist && value <= v { if v, exist := mappedLocations[pt.normalize(location)]; exist && value <= v {
continue continue
} }
mappedLocations[pt.normalize(path)] = value mappedLocations[pt.normalize(location)] = value
} }
pt.mappedLocations = mappedLocations pt.mappedLocations = mappedLocations
@ -660,9 +664,9 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
} }
if pt.cygPath { if pt.cygPath {
path, err := pt.env.RunCommand("cygpath", "-u", inputPath) cygPath, err := pt.env.RunCommand("cygpath", "-u", inputPath)
if len(path) != 0 { if len(cygPath) != 0 {
inputPath = path inputPath = cygPath
pt.pathSeparator = "/" pt.pathSeparator = "/"
} }
@ -697,25 +701,26 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
return root, relative return root, relative
} }
func (pt *Path) isRootFS(path string) bool { func (pt *Path) isRootFS(inputPath string) bool {
return len(path) == 1 && runtime.IsPathSeparator(pt.env, path[0]) return len(inputPath) == 1 && path.IsSeparator(inputPath[0])
} }
func (pt *Path) endWithSeparator(path string) bool { func (pt *Path) endWithSeparator(inputPath string) bool {
if len(path) == 0 { if len(inputPath) == 0 {
return false return false
} }
return runtime.IsPathSeparator(pt.env, path[len(path)-1])
return path.IsSeparator(inputPath[len(inputPath)-1])
} }
func (pt *Path) normalize(inputPath string) string { func (pt *Path) normalize(inputPath string) string {
normalized := inputPath normalized := inputPath
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || runtime.IsPathSeparator(pt.env, normalized[1])) { if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || path.IsSeparator(normalized[1])) {
normalized = pt.env.Home() + normalized[1:] normalized = pt.env.Home() + normalized[1:]
} }
normalized = runtime.CleanPath(pt.env, normalized) normalized = path.Clean(normalized)
if pt.env.GOOS() == runtime.WINDOWS || pt.env.GOOS() == runtime.DARWIN { if pt.env.GOOS() == runtime.WINDOWS || pt.env.GOOS() == runtime.DARWIN {
normalized = strings.ToLower(normalized) normalized = strings.ToLower(normalized)
@ -827,9 +832,9 @@ func (pt *Path) makeFolderFormatMap() map[string]string {
if gitDirFormat := pt.props.GetString(GitDirFormat, ""); len(gitDirFormat) != 0 { if gitDirFormat := pt.props.GetString(GitDirFormat, ""); len(gitDirFormat) != 0 {
dir, err := pt.env.HasParentFilePath(".git", false) dir, err := pt.env.HasParentFilePath(".git", false)
if err == nil && dir.IsDir { if err == nil && dir.IsDir {
// Make it consistent with the modified path. // Make it consistent with the modified parent.
path := pt.join(pt.replaceMappedLocations(dir.ParentFolder)) parent := pt.join(pt.replaceMappedLocations(dir.ParentFolder))
folderFormatMap[path] = gitDirFormat folderFormatMap[parent] = gitDirFormat
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,812 @@
//go:build !windows
package segments
import (
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
var testParentCases = []testParentCase{
{
Case: "Inside Home folder",
Expected: "~/",
HomePath: homeDir,
Pwd: homeDir + "/test",
GOOS: runtime.DARWIN,
PathSeparator: "/",
},
{
Case: "Home folder",
HomePath: homeDir,
Pwd: homeDir,
GOOS: runtime.DARWIN,
PathSeparator: "/",
},
{
Case: "Home folder with a trailing separator",
HomePath: homeDir,
Pwd: homeDir + "/",
GOOS: runtime.DARWIN,
PathSeparator: "/",
},
{
Case: "Root",
HomePath: homeDir,
Pwd: "/",
GOOS: runtime.DARWIN,
PathSeparator: "/",
},
{
Case: "Root + 1",
Expected: "/",
HomePath: homeDir,
Pwd: "/usr",
GOOS: runtime.DARWIN,
PathSeparator: "/",
},
}
var testAgnosterPathStyleCases = []testAgnosterPathStyleCase{
{
Style: Unique,
Expected: "~ > a > ab > abcd",
HomePath: homeDir,
Pwd: homeDir + "/ab/abc/abcd",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Unique,
Expected: "~ > a > .a > abcd",
HomePath: homeDir,
Pwd: homeDir + "/ab/.abc/abcd",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Unique,
Expected: "~ > a > ab > abcd",
HomePath: homeDir,
Pwd: homeDir + "/ab/ab/abcd",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Unique,
Expected: "a",
HomePath: homeDir,
Pwd: "/ab",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Powerlevel,
Expected: "t > w > o > a > v > l > p > wh > we > i > wa > th > the > d > f > u > it > c > to > a > co > stream",
HomePath: homeDir,
Pwd: "/there/was/once/a/very/long/path/which/wended/its/way/through/the/dark/forest/until/it/came/to/a/cold/stream",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxWidth: 20,
},
{
Style: Powerlevel,
Expected: "t > w > o > a > v > l > p > which > wended > its > way > through > the",
HomePath: homeDir,
Pwd: "/there/was/once/a/very/long/path/which/wended/its/way/through/the",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxWidth: 70,
},
{
Style: Powerlevel,
Expected: "var/cache/pacman",
HomePath: homeDir,
Pwd: "/var/cache/pacman",
PathSeparator: "/",
FolderSeparatorIcon: "/",
MaxWidth: 50,
},
{
Style: Letter,
Expected: "~",
HomePath: homeDir,
Pwd: homeDir,
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "~ > a > w > man",
HomePath: homeDir,
Pwd: homeDir + "/ab/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > b > a > w > man",
HomePath: homeDir,
Pwd: "/usr/burp/ab/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > a > w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/ab/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > a > .w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/ab/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > a > ._w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/ab/._whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .ä > ū > .w > man",
HomePath: homeDir,
Pwd: "/usr/.äufbau/ūmgebung/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > 1 > .w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/12345/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > 1 > .w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/12345abc/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "u > .b > __p > .w > man",
HomePath: homeDir,
Pwd: "/usr/.burp/__pycache__/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "➼ > .w > man",
HomePath: homeDir,
Pwd: "/➼/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "➼ s > .w > man",
HomePath: homeDir,
Pwd: "/➼ something/.whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "w",
HomePath: homeDir,
Pwd: "/whatever",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "~ > .. > man",
HomePath: homeDir,
Pwd: homeDir + "/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "~ > ab > .. > man",
HomePath: homeDir,
Pwd: homeDir + "/ab/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "usr > foo > bar > .. > man",
HomePath: homeDir,
Pwd: "/usr/foo/bar/foobar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "whatever > .. > foo > bar",
HomePath: homeDir,
Pwd: "/whatever/foobar/foo/bar",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterFull,
Expected: "usr > location > whatever",
HomePath: homeDir,
Pwd: "/usr/location/whatever",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterFull,
Expected: "PSDRIVE: | src",
HomePath: homeDir,
Pwd: "/foo",
Pswd: "PSDRIVE:/src",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
},
{
Style: AgnosterShort,
Expected: ".. | src | init",
HomePath: homeDir,
Pwd: "/foo",
Pswd: "PSDRIVE:/src/init",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "usr > foo > bar > man",
HomePath: homeDir,
Pwd: "/usr/foo/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
},
{
Style: AgnosterShort,
Expected: "PSDRIVE: | src",
HomePath: homeDir,
Pwd: "/foo",
Pswd: "PSDRIVE:/src",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "~ > projects",
HomePath: homeDir,
Pwd: homeDir + "/projects",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "~",
HomePath: homeDir,
Pwd: homeDir,
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 1,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "usr > .. > bar > man",
HomePath: homeDir,
Pwd: "/usr/foo/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
},
{
Style: AgnosterShort,
Expected: "~ > .. > man",
HomePath: homeDir,
Pwd: homeDir + "/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "usr > .. > man",
HomePath: homeDir,
Pwd: "/usr/location/whatever/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "~ > .. > bar > man",
HomePath: homeDir,
Pwd: homeDir + "/foo/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
},
{
Style: AgnosterShort,
Expected: "~ > foo > bar > man",
HomePath: homeDir,
Pwd: homeDir + "/foo/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
},
{
Style: AgnosterShort,
Expected: "PSDRIVE: | .. | init",
HomePath: homeDir,
Pwd: "/foo",
Pswd: "PSDRIVE:/src/init",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
},
{
Style: AgnosterShort,
Expected: ".. > foo",
HomePath: homeDir,
Pwd: homeDir + "/foo",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 1,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: ".. > bar > man",
HomePath: homeDir,
Pwd: homeDir + "/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: ".. > foo > bar > man",
HomePath: homeDir,
Pwd: "/usr/foo/bar/man",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "~ > foo",
HomePath: homeDir,
Pwd: homeDir + "/foo",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "~ > foo > bar",
HomePath: homeDir,
Pwd: homeDir + "/foo/bar",
PathSeparator: "/",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "C: | ",
HomePath: homeDir,
Pwd: "/mnt/c",
Pswd: "C:",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "~ | space foo",
HomePath: homeDir,
Pwd: homeDir + "/space foo",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: ".. | space foo",
HomePath: homeDir,
Pwd: homeDir + "/space foo",
PathSeparator: "/",
FolderSeparatorIcon: " | ",
MaxDepth: 1,
HideRootLocation: true,
},
}
var testAgnosterPathCases = []testAgnosterPathCase{
{
Case: "Unix outside home",
Expected: "mnt > f > f > location",
Home: homeDir,
PWD: "/mnt/go/test/location",
PathSeparator: "/",
},
{
Case: "Unix inside home",
Expected: "~ > f > f > location",
Home: homeDir,
PWD: homeDir + "/docs/jan/location",
PathSeparator: "/",
},
{
Case: "Unix outside home zero levels",
Expected: "mnt > location",
Home: homeDir,
PWD: "/mnt/location",
PathSeparator: "/",
},
{
Case: "Unix outside home one level",
Expected: "mnt > f > location",
Home: homeDir,
PWD: "/mnt/folder/location",
PathSeparator: "/",
},
{
Case: "Unix, colorize",
Expected: "<blue>mnt</> > <yellow>f</> > <blue>location</>",
Home: homeDir,
PWD: "/mnt/folder/location",
PathSeparator: "/",
Cycle: []string{"blue", "yellow"},
},
{
Case: "Unix, colorize with folder separator",
Expected: "<blue>mnt</><yellow> > </><yellow>f</><blue> > </><blue>location</>",
Home: homeDir,
PWD: "/mnt/folder/location",
PathSeparator: "/",
Cycle: []string{"blue", "yellow"},
ColorSeparator: true,
},
{
Case: "Unix one level",
Expected: "mnt",
Home: homeDir,
PWD: "/mnt",
PathSeparator: "/",
},
}
var testAgnosterLeftPathCases = []testAgnosterLeftPathCase{
{
Case: "Unix outside home",
Expected: "mnt > go > f > f",
Home: homeDir,
PWD: "/mnt/go/test/location",
PathSeparator: "/",
},
{
Case: "Unix inside home",
Expected: "~ > docs > f > f",
Home: homeDir,
PWD: homeDir + "/docs/jan/location",
PathSeparator: "/",
},
{
Case: "Unix outside home zero levels",
Expected: "mnt > location",
Home: homeDir,
PWD: "/mnt/location",
PathSeparator: "/",
},
{
Case: "Unix outside home one level",
Expected: "mnt > folder > f",
Home: homeDir,
PWD: "/mnt/folder/location",
PathSeparator: "/",
},
}
var testFullAndFolderPathCases = []testFullAndFolderPathCase{
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"},
{Style: Full, Pwd: "/", Expected: "/"},
{Style: Full, Pwd: homeDir, Expected: "~"},
{Style: Full, Pwd: homeDir + abc, Expected: "~/abc"},
{Style: Full, Pwd: homeDir + abc, Expected: homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, Expected: abcd},
{Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"},
{Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "/home|someone", DisableMappedLocations: true},
{Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "~|abc"},
{Style: Full, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "/a|b|c|d"},
{Style: FolderType, Pwd: "/", Expected: "/"},
{Style: FolderType, Pwd: homeDir, Expected: "~"},
{Style: FolderType, Pwd: homeDir, Expected: "someone", DisableMappedLocations: true},
{Style: FolderType, Pwd: homeDir + abc, Expected: "abc"},
{Style: FolderType, Pwd: abcd, Expected: "d"},
{Style: FolderType, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"},
{Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"},
{Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "someone", DisableMappedLocations: true},
{Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "abc"},
{Style: FolderType, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "d"},
// StackCountEnabled=true and StackCount=2
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 2, Expected: "2 /"},
{Style: Full, Pwd: "/", StackCount: 2, Expected: "2 /"},
{Style: Full, Pwd: homeDir, StackCount: 2, Expected: "2 ~"},
{Style: Full, Pwd: homeDir + abc, StackCount: 2, Expected: "2 ~/abc"},
{Style: Full, Pwd: homeDir + abc, StackCount: 2, Expected: "2 " + homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, StackCount: 2, Expected: "2 /a/b/c/d"},
// StackCountEnabled=false and StackCount=2
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Template: "{{ .Path }}", StackCount: 2, Expected: "/"},
{Style: Full, Pwd: "/", Template: "{{ .Path }}", StackCount: 2, Expected: "/"},
{Style: Full, Pwd: homeDir, Template: "{{ .Path }}", StackCount: 2, Expected: "~"},
{Style: Full, Pwd: homeDir + abc, Template: "{{ .Path }}", StackCount: 2, Expected: homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, Template: "{{ .Path }}", StackCount: 2, Expected: abcd},
// StackCountEnabled=true and StackCount=0
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 0, Expected: "/"},
{Style: Full, Pwd: "/", StackCount: 0, Expected: "/"},
{Style: Full, Pwd: homeDir, StackCount: 0, Expected: "~"},
{Style: Full, Pwd: homeDir + abc, StackCount: 0, Expected: "~/abc"},
{Style: Full, Pwd: homeDir + abc, StackCount: 0, Expected: homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, StackCount: 0, Expected: abcd},
// StackCountEnabled=true and StackCount<0
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: -1, Expected: "/"},
{Style: Full, Pwd: "/", StackCount: -1, Expected: "/"},
{Style: Full, Pwd: homeDir, StackCount: -1, Expected: "~"},
{Style: Full, Pwd: homeDir + abc, StackCount: -1, Expected: "~/abc"},
{Style: Full, Pwd: homeDir + abc, StackCount: -1, Expected: homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, StackCount: -1, Expected: abcd},
// StackCountEnabled=true and StackCount not set
{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"},
{Style: Full, Pwd: "/", Expected: "/"},
{Style: Full, Pwd: homeDir, Expected: "~"},
{Style: Full, Pwd: homeDir + abc, Expected: "~/abc"},
{Style: Full, Pwd: homeDir + abc, Expected: homeDir + abc, DisableMappedLocations: true},
{Style: Full, Pwd: abcd, Expected: abcd},
}
var testFullPathCustomMappedLocationsCases = []testFullPathCustomMappedLocationsCase{
{Pwd: homeDir + "/d", MappedLocations: map[string]string{"{{ .Env.HOME }}/d": "#"}, Expected: "#"},
{Pwd: abcd, MappedLocations: map[string]string{abcd: "#"}, Expected: "#"},
{Pwd: abcd, MappedLocations: map[string]string{"/a/b": "#"}, Expected: "#/c/d"},
{Pwd: abcd, MappedLocations: map[string]string{"/a/b": "/e/f"}, Expected: "/e/f/c/d"},
{Pwd: homeDir + abcd, MappedLocations: map[string]string{"~/a/b": "#"}, Expected: "#/c/d"},
{Pwd: "/a" + homeDir + "/b/c/d", MappedLocations: map[string]string{"/a~": "#"}, Expected: "/a" + homeDir + "/b/c/d"},
{Pwd: homeDir + abcd, MappedLocations: map[string]string{"/a/b": "#"}, Expected: homeDir + abcd},
}
var testSplitPathCases = []testSplitPathCase{
{Case: "Root directory", Root: "/", Expected: Folders{}},
{
Case: "Regular directory",
Root: "/",
Relative: "c/d",
GOOS: runtime.DARWIN,
Expected: Folders{
{Name: "c", Path: "/c"},
{Name: "d", Path: "/c/d"},
},
},
{
Case: "Home directory - git folder",
Root: "~",
Relative: "c/d",
GOOS: runtime.DARWIN,
GitDir: &runtime.FileInfo{IsDir: true, ParentFolder: "/a/b/c"},
GitDirFormat: "<b>%s</b>",
Expected: Folders{
{Name: "<b>c</b>", Path: "~/c", Display: true},
{Name: "d", Path: "~/c/d"},
},
},
}
var testNormalizePathCases = []testNormalizePathCase{
{
Case: "Linux: home prefix, backslash included",
Input: "~/Bob\\Foo",
HomeDir: homeDir,
GOOS: runtime.LINUX,
Expected: homeDir + "/Bob\\Foo",
},
{
Case: "macOS: home prefix, backslash included",
Input: "~/Bob\\Foo",
HomeDir: homeDir,
GOOS: runtime.DARWIN,
Expected: homeDir + "/bob\\foo",
},
{
Case: "Linux: absolute",
Input: "/foo/~/bar",
HomeDir: homeDir,
GOOS: runtime.LINUX,
Expected: "/foo/~/bar",
},
{
Case: "Linux: home prefix",
Input: "~/baz",
HomeDir: homeDir,
GOOS: runtime.LINUX,
Expected: homeDir + "/baz",
},
}
func TestFolderPathCustomMappedLocations(t *testing.T) {
pwd := abcd
env := new(mock.Environment)
env.On("PathSeparator").Return("/")
env.On("Home").Return(homeDir)
env.On("Pwd").Return(pwd)
env.On("GOOS").Return("")
args := &runtime.Flags{
PSWD: pwd,
}
env.On("Flags").Return(args)
env.On("Shell").Return(shell.GENERIC)
template.Cache = new(cache.Template)
template.Init(env, nil)
props := properties.Map{
properties.Style: FolderType,
MappedLocations: map[string]string{
abcd: "#",
},
}
path := &Path{}
path.Init(props, env)
path.setPaths()
path.setStyle()
got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path)
assert.Equal(t, "#", got)
}
func TestReplaceMappedLocations(t *testing.T) {
cases := []struct {
Case string
Pwd string
Expected string
MappedLocationsEnabled bool
}{
{Pwd: "/c/l/k/f", Expected: "f"},
{Pwd: "/f/g/h", Expected: "/f/g/h"},
{Pwd: "/f/g/h/e", Expected: "^/e"},
{Pwd: abcd, Expected: "#"},
{Pwd: "/a/b/c/d/e", Expected: "#/e"},
{Pwd: "/a/b/c/D/e", Expected: "#/e"},
{Pwd: "/a/b/k/j/e", Expected: "e"},
{Pwd: "/a/b/k/l", Expected: "@/l"},
{Pwd: "/a/b/k/l", MappedLocationsEnabled: true, Expected: "~/l"},
}
for _, tc := range cases {
env := new(mock.Environment)
env.On("PathSeparator").Return("/")
env.On("Pwd").Return(tc.Pwd)
env.On("Shell").Return(shell.FISH)
env.On("GOOS").Return(runtime.DARWIN)
env.On("Home").Return("/a/b/k")
template.Cache = new(cache.Template)
template.Init(env, nil)
props := properties.Map{
MappedLocationsEnabled: tc.MappedLocationsEnabled,
MappedLocations: map[string]string{
abcd: "#",
"/f/g/h/*": "^",
"/c/l/k/*": "",
"~": "@",
"~/j/*": "",
},
}
path := &Path{}
path.Init(props, env)
path.setPaths()
assert.Equal(t, tc.Expected, path.pwd)
}
}
func TestGetPwd(t *testing.T) {
cases := []struct {
Pwd string
Pswd string
Expected string
MappedLocationsEnabled bool
}{
{MappedLocationsEnabled: true, Pwd: homeDir, Expected: "~"},
{MappedLocationsEnabled: true, Pwd: homeDir + "-test", Expected: homeDir + "-test"},
{MappedLocationsEnabled: true},
{MappedLocationsEnabled: true, Pwd: "/usr", Expected: "/usr"},
{MappedLocationsEnabled: true, Pwd: homeDir + abc, Expected: "~/abc"},
{MappedLocationsEnabled: true, Pwd: abcd, Expected: "#"},
{MappedLocationsEnabled: true, Pwd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"},
{MappedLocationsEnabled: true, Pwd: "/z/y/x/w", Expected: "/z/y/x/w"},
{MappedLocationsEnabled: false},
{MappedLocationsEnabled: false, Pwd: homeDir + abc, Expected: homeDir + abc},
{MappedLocationsEnabled: false, Pwd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"},
{MappedLocationsEnabled: false, Pwd: homeDir + cdefg, Expected: homeDir + cdefg},
{MappedLocationsEnabled: true, Pwd: homeDir + cdefg, Expected: "~/c/d/e/f/g"},
{MappedLocationsEnabled: true, Pwd: "/w/d/x/w", Pswd: "/z/y/x/w", Expected: "/z/y/x/w"},
{MappedLocationsEnabled: false, Pwd: "/f/g/k/d/e/f/g", Pswd: "/a/b/c/d/e/f/g", Expected: "#/e/f/g"},
}
for _, tc := range cases {
env := new(mock.Environment)
env.On("PathSeparator").Return("/")
env.On("Home").Return(homeDir)
env.On("Pwd").Return(tc.Pwd)
env.On("GOOS").Return("")
args := &runtime.Flags{
PSWD: tc.Pswd,
}
env.On("Flags").Return(args)
env.On("Shell").Return(shell.PWSH)
template.Cache = new(cache.Template)
template.Init(env, nil)
props := properties.Map{
MappedLocationsEnabled: tc.MappedLocationsEnabled,
MappedLocations: map[string]string{
abcd: "#",
},
}
path := &Path{}
path.Init(props, env)
path.setPaths()
assert.Equal(t, tc.Expected, path.pwd)
}
}

View file

@ -0,0 +1,524 @@
package segments
import (
"errors"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
)
var testParentCases = []testParentCase{
{
Case: "Windows Home folder",
HomePath: homeDirWindows,
Pwd: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows drive root",
HomePath: homeDirWindows,
Pwd: "C:",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows drive root with a trailing separator",
HomePath: homeDirWindows,
Pwd: "C:\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows drive root + 1",
Expected: "C:\\",
HomePath: homeDirWindows,
Pwd: "C:\\test",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "PSDrive root",
HomePath: homeDirWindows,
Pwd: "HKLM:",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
}
var testAgnosterPathStyleCases = []testAgnosterPathStyleCase{
{
Style: Unique,
Expected: "C > a > ab > abcd",
HomePath: homeDirWindows,
Pwd: "C:\\ab\\ab\\abcd",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "C: > ",
HomePath: homeDirWindows,
Pwd: "C:\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "C > s > .w > man",
HomePath: homeDirWindows,
Pwd: "C:\\something\\.whatever\\man",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Letter,
Expected: "~ > s > man",
HomePath: homeDirWindows,
Pwd: homeDirWindows + "\\something\\man",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "C: > .. > foo > .. > man",
HomePath: homeDirWindows,
Pwd: "C:\\Users\\foo\\foobar\\man",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "c > .. > foo > .. > man",
HomePath: homeDirWindows,
Pwd: "C:\\Users\\foo\\foobar\\man",
GOOS: runtime.WINDOWS,
Shell: shell.BASH,
Cygwin: true,
Cygpath: "/c/Users/foo/foobar/man",
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "C: > .. > foo > .. > man",
HomePath: homeDirWindows,
Pwd: "C:\\Users\\foo\\foobar\\man",
GOOS: runtime.WINDOWS,
Shell: shell.BASH,
CygpathError: errors.New("oh no"),
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "\\\\localhost\\c$ > some",
HomePath: homeDirWindows,
Pwd: "\\\\localhost\\c$\\some",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "\\\\localhost\\c$",
HomePath: homeDirWindows,
Pwd: "\\\\localhost\\c$",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: ".. > bar > man",
HomePath: homeDirWindows,
Pwd: homeDirWindows + fooBarMan,
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "C: > ",
HomePath: homeDirWindows,
Pwd: "C:",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterShort,
Expected: "C: > .. > bar > man",
HomePath: homeDirWindows,
Pwd: "C:\\usr\\foo\\bar\\man",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
},
{
Style: AgnosterShort,
Expected: "C: > .. > foo > bar > man",
HomePath: homeDirWindows,
Pwd: "C:\\usr\\foo\\bar\\man",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
},
{
Style: AgnosterShort,
Expected: "~ > .. > bar > man",
HomePath: homeDirWindows,
Pwd: homeDirWindows + fooBarMan,
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
},
{
Style: AgnosterShort,
Expected: "~ > foo > bar > man",
HomePath: homeDirWindows,
Pwd: homeDirWindows + fooBarMan,
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 3,
},
{
Style: AgnosterShort,
Expected: "~",
HomePath: homeDirWindows,
Pwd: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 1,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: ".. > foo",
HomePath: homeDirWindows,
Pwd: homeDirWindows + "\\foo",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 1,
HideRootLocation: true,
},
{
Style: AgnosterShort,
Expected: "~ > foo",
HomePath: homeDirWindows,
Pwd: homeDirWindows + "\\foo",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
MaxDepth: 2,
HideRootLocation: true,
},
}
var testAgnosterPathCases = []testAgnosterPathCase{
{
Case: "Windows registry drive case sensitive",
Expected: "\uf013 > f > magnetic:TOAST",
Home: homeDirWindows,
PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows outside home",
Expected: "C: > f > f > location",
Home: homeDirWindows,
PWD: "C:\\Program Files\\Go\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows oustide home",
Expected: "~ > f > f > location",
Home: homeDirWindows,
PWD: homeDirWindows + "\\Documents\\Bill\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows inside home zero levels",
Expected: "C: > location",
Home: homeDirWindows,
PWD: "C:\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows inside home one level",
Expected: "C: > f > location",
Home: homeDirWindows,
PWD: "C:\\Program Files\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower case drive letter",
Expected: "C: > Windows",
Home: homeDirWindows,
PWD: "C:\\Windows\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower case drive letter (other)",
Expected: "P: > Other",
Home: homeDirWindows,
PWD: "P:\\Other\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive",
Expected: "some: > some",
Home: homeDirWindows,
PWD: "some:\\some\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive (ending with c)",
Expected: "src: > source",
Home: homeDirWindows,
PWD: "src:\\source\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive (arbitrary cases)",
Expected: "sRc: > source",
Home: homeDirWindows,
PWD: "sRc:\\source\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows registry drive",
Expected: "\uf013 > f > magnetic:test",
Home: homeDirWindows,
PWD: "HKLM:\\SOFTWARE\\magnetic:test\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
}
var testAgnosterLeftPathCases = []testAgnosterLeftPathCase{
{
Case: "Windows inside home",
Expected: "~ > Documents > f > f",
Home: homeDirWindows,
PWD: homeDirWindows + "\\Documents\\Bill\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows outside home",
Expected: "C: > Program Files > f > f",
Home: homeDirWindows,
PWD: "C:\\Program Files\\Go\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows inside home zero levels",
Expected: "C: > location",
Home: homeDirWindows,
PWD: "C:\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows inside home one level",
Expected: "C: > Program Files > f",
Home: homeDirWindows,
PWD: "C:\\Program Files\\location",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower case drive letter",
Expected: "C: > Windows",
Home: homeDirWindows,
PWD: "C:\\Windows\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower case drive letter (other)",
Expected: "P: > Other",
Home: homeDirWindows,
PWD: "P:\\Other\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive",
Expected: "some: > some",
Home: homeDirWindows,
PWD: "some:\\some\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive (ending with c)",
Expected: "src: > source",
Home: homeDirWindows,
PWD: "src:\\source\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows lower word drive (arbitrary cases)",
Expected: "sRc: > source",
Home: homeDirWindows,
PWD: "sRc:\\source\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows registry drive",
Expected: "\uf013 > SOFTWARE > f",
Home: homeDirWindows,
PWD: "HKLM:\\SOFTWARE\\magnetic:test\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
{
Case: "Windows registry drive case sensitive",
Expected: "\uf013 > SOFTWARE > f",
Home: homeDirWindows,
PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\",
GOOS: runtime.WINDOWS,
PathSeparator: "\\",
},
}
var testFullAndFolderPathCases = []testFullAndFolderPathCase{
{Style: FolderType, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: runtime.WINDOWS},
{Style: FolderType, FolderSeparatorIcon: "\\", Pwd: "\\\\localhost\\d$", Expected: "\\\\localhost\\d$", PathSeparator: "\\", GOOS: runtime.WINDOWS},
{Style: FolderType, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: runtime.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: runtime.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows + "\\abc", Expected: "~\\abc", PathSeparator: "\\", GOOS: runtime.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\posh", Expected: "C:\\Users\\posh", PathSeparator: "\\", GOOS: runtime.WINDOWS},
}
var testFullPathCustomMappedLocationsCases = []testFullPathCustomMappedLocationsCase{
{Pwd: "\\a\\b\\c\\d", MappedLocations: map[string]string{"\\a\\b": "#"}, GOOS: runtime.WINDOWS, PathSeparator: "\\", Expected: "#\\c\\d"},
}
var testSplitPathCases = []testSplitPathCase{
{
Case: "Home directory - git folder on Windows",
Root: "C:",
Relative: "a/b/c/d",
GOOS: runtime.WINDOWS,
GitDir: &runtime.FileInfo{IsDir: true, ParentFolder: "C:/a/b/c"},
GitDirFormat: "<b>%s</b>",
Expected: Folders{
{Name: "a", Path: "C:/a"},
{Name: "b", Path: "C:/a/b"},
{Name: "<b>c</b>", Path: "C:/a/b/c", Display: true},
{Name: "d", Path: "C:/a/b/c/d"},
},
},
}
var testNormalizePathCases = []testNormalizePathCase{
{
Case: "Windows: absolute w/o drive letter, forward slash included",
Input: "/foo/~/bar",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\foo\\~\\bar",
},
{
Case: "Windows: absolute",
Input: homeDirWindows + "\\Foo",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "c:\\users\\someone\\foo",
},
{
Case: "Windows: home prefix",
Input: "~\\Bob\\Foo",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "c:\\users\\someone\\bob\\foo",
},
{
Case: "Windows: home prefix",
Input: "~/baz",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "c:\\users\\someone\\baz",
},
{
Case: "Windows: UNC root w/ prefix",
Input: `\\.\UNC\localhost\c$`,
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$",
},
{
Case: "Windows: UNC root w/ prefix, forward slash included",
Input: "//./UNC/localhost/c$",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$",
},
{
Case: "Windows: UNC root",
Input: `\\localhost\c$\`,
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$",
},
{
Case: "Windows: UNC root, forward slash included",
Input: "//localhost/c$",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$",
},
{
Case: "Windows: UNC",
Input: `\\localhost\c$\some`,
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$\\some",
},
{
Case: "Windows: UNC, forward slash included",
Input: "//localhost/c$/some",
HomeDir: homeDirWindows,
GOOS: runtime.WINDOWS,
PathSeparator: `\`,
Expected: "\\\\localhost\\c$\\some",
},
}

View file

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -135,10 +136,15 @@ func (p *Pulumi) getProjectName() error {
p.Name = pulumiFileSpec.Name p.Name = pulumiFileSpec.Name
sha1HexString := func(value string) string { p.workspaceSHA1 = p.sha1HexString(p.env.Pwd() + path.Separator() + fileName)
return nil
}
func (p *Pulumi) sha1HexString(s string) string {
h := sha1.New() h := sha1.New()
_, err := h.Write([]byte(value)) _, err := h.Write([]byte(s))
if err != nil { if err != nil {
p.env.Error(err) p.env.Error(err)
return "" return ""
@ -147,11 +153,6 @@ func (p *Pulumi) getProjectName() error {
return hex.EncodeToString(h.Sum(nil)) return hex.EncodeToString(h.Sum(nil))
} }
p.workspaceSHA1 = sha1HexString(p.env.Pwd() + p.env.PathSeparator() + fileName)
return nil
}
func (p *Pulumi) getPulumiAbout() { func (p *Pulumi) getPulumiAbout() {
if len(p.Stack) == 0 { if len(p.Stack) == 0 {
p.env.Error(fmt.Errorf("pulumi stack name is empty, use `fetch_stack` property to enable stack fetching")) p.env.Error(fmt.Errorf("pulumi stack name is empty, use `fetch_stack` property to enable stack fetching"))

View file

@ -2,11 +2,13 @@ package segments
import ( import (
"errors" "errors"
"fmt"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock" testify_ "github.com/stretchr/testify/mock"
) )
@ -161,7 +163,8 @@ description: A Console App
env.On("RunCommand", "pulumi", []string{"stack", "ls", "--json"}).Return(tc.Stack, tc.StackError) env.On("RunCommand", "pulumi", []string{"stack", "ls", "--json"}).Return(tc.Stack, tc.StackError)
env.On("RunCommand", "pulumi", []string{"about", "--json"}).Return(tc.About, tc.AboutError) env.On("RunCommand", "pulumi", []string{"about", "--json"}).Return(tc.About, tc.AboutError)
env.On("Pwd").Return("/home/foobar/Work/oh-my-posh/pulumi/projects/awesome-project") pwd := "/home/foobar/Work/oh-my-posh/pulumi/projects/awesome-project"
env.On("Pwd").Return(pwd)
env.On("Home").Return(filepath.Clean("/home/foobar")) env.On("Home").Return(filepath.Clean("/home/foobar"))
env.On("Error", testify_.Anything) env.On("Error", testify_.Anything)
env.On("Debug", testify_.Anything) env.On("Debug", testify_.Anything)
@ -173,11 +176,22 @@ description: A Console App
env.On("HasFiles", pulumiJSON).Return(len(tc.JSONConfig) > 0) env.On("HasFiles", pulumiJSON).Return(len(tc.JSONConfig) > 0)
env.On("FileContent", pulumiJSON).Return(tc.JSONConfig, nil) env.On("FileContent", pulumiJSON).Return(tc.JSONConfig, nil)
env.On("PathSeparator").Return("/")
env.On("HasFolder", filepath.Clean("/home/foobar/.pulumi/workspaces")).Return(tc.HasWorkspaceFolder) env.On("HasFolder", filepath.Clean("/home/foobar/.pulumi/workspaces")).Return(tc.HasWorkspaceFolder)
workspaceFile := "oh-my-posh-c62b7b6786c5c5a85896576e46a25d7c9f888e92-workspace.json"
pulumi := &Pulumi{}
var fileName string
if len(tc.JSONConfig) > 0 {
fileName = pulumiJSON
} else {
fileName = pulumiYAML
}
sha1 := pulumi.sha1HexString(pwd + path.Separator() + fileName)
workspaceFile := fmt.Sprintf("oh-my-posh-%s-workspace.json", sha1)
env.On("HasFilesInDir", filepath.Clean("/home/foobar/.pulumi/workspaces"), workspaceFile).Return(len(tc.WorkSpaceFile) > 0) env.On("HasFilesInDir", filepath.Clean("/home/foobar/.pulumi/workspaces"), workspaceFile).Return(len(tc.WorkSpaceFile) > 0)
env.On("FileContent", filepath.Clean("/home/foobar/.pulumi/workspaces/"+workspaceFile)).Return(tc.WorkSpaceFile, nil) env.On("FileContent", filepath.Clean("/home/foobar/.pulumi/workspaces/"+workspaceFile)).Return(tc.WorkSpaceFile, nil)
props := properties.Map{ props := properties.Map{
@ -185,7 +199,6 @@ description: A Console App
FetchAbout: tc.FetchAbout, FetchAbout: tc.FetchAbout,
} }
pulumi := &Pulumi{}
pulumi.Init(props, env) pulumi.Init(props, env)
assert.Equal(t, tc.ExpectedEnabled, pulumi.Enabled(), tc.Case) assert.Equal(t, tc.ExpectedEnabled, pulumi.Enabled(), tc.Case)

View file

@ -8,7 +8,7 @@ import (
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
) )
type Python struct { type Python struct {
@ -87,10 +87,10 @@ func (p *Python) loadContext() {
continue continue
} }
name := runtime.Base(p.language.env, venv) name := path.Base(venv)
if folderNameFallback && slices.Contains(defaultVenvNames, name) { if folderNameFallback && slices.Contains(defaultVenvNames, name) {
venv = strings.TrimSuffix(venv, name) venv = strings.TrimSuffix(venv, name)
name = runtime.Base(p.language.env, venv) name = path.Base(venv)
} }
if p.canUseVenvName(name) { if p.canUseVenvName(name) {
@ -121,23 +121,27 @@ func (p *Python) pyenvVersion() (string, error) {
// Use `pyenv root` instead of $PYENV_ROOT? // Use `pyenv root` instead of $PYENV_ROOT?
// Is our Python executable at $PYENV_ROOT/bin/python ? // Is our Python executable at $PYENV_ROOT/bin/python ?
// Should p.env expose command paths? // Should p.env expose command paths?
path := p.env.CommandPath("python") cmdPath := p.env.CommandPath("python")
if len(path) == 0 { if len(cmdPath) == 0 {
path = p.env.CommandPath("python3") cmdPath = p.env.CommandPath("python3")
} }
if len(path) == 0 {
if len(cmdPath) == 0 {
return "", errors.New("no python executable found") return "", errors.New("no python executable found")
} }
pyEnvRoot := p.env.Getenv("PYENV_ROOT") pyEnvRoot := p.env.Getenv("PYENV_ROOT")
// TODO: pyenv-win has this at $PYENV_ROOT/pyenv-win/shims // TODO: pyenv-win has this at $PYENV_ROOT/pyenv-win/shims
if path != filepath.Join(pyEnvRoot, "shims", "python") { if cmdPath != filepath.Join(pyEnvRoot, "shims", "python") {
return "", fmt.Errorf("executable at %s is not a pyenv shim", path) return "", fmt.Errorf("executable at %s is not a pyenv shim", cmdPath)
} }
// pyenv version-name will return current version or virtualenv // pyenv version-name will return current version or virtualenv
cmdOutput, err := p.env.RunCommand("pyenv", "version-name") cmdOutput, err := p.env.RunCommand("pyenv", "version-name")
if err != nil { if err != nil {
return "", err return "", err
} }
versionString := strings.Split(cmdOutput, ":")[0] versionString := strings.Split(cmdOutput, ":")[0]
if len(versionString) == 0 { if len(versionString) == 0 {
return "", errors.New("no pyenv version-name found") return "", errors.New("no pyenv version-name found")
@ -148,34 +152,41 @@ func (p *Python) pyenvVersion() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
// ../versions/(version)[/envs/(virtualenv)] // ../versions/(version)[/envs/(virtualenv)]
shortPath, err := filepath.Rel(filepath.Join(pyEnvRoot, "versions"), realPath) shortPath, err := filepath.Rel(filepath.Join(pyEnvRoot, "versions"), realPath)
if err != nil { if err != nil {
return "", err return "", err
} }
// override virtualenv if pyenv set one // override virtualenv if pyenv set one
parts := strings.Split(shortPath, string(filepath.Separator)) parts := strings.Split(shortPath, string(filepath.Separator))
if len(parts) > 2 && p.canUseVenvName(parts[2]) { if len(parts) > 2 && p.canUseVenvName(parts[2]) {
p.Venv = parts[2] p.Venv = parts[2]
} }
return parts[0], nil return parts[0], nil
} }
func (p *Python) pyvenvCfgPrompt() string { func (p *Python) pyvenvCfgPrompt() string {
path := p.language.env.CommandPath("python") cmdPath := p.language.env.CommandPath("python")
if len(path) == 0 { if len(cmdPath) == 0 {
path = p.language.env.CommandPath("python3") cmdPath = p.language.env.CommandPath("python3")
} }
if len(path) == 0 {
if len(cmdPath) == 0 {
return "" return ""
} }
pyvenvDir := filepath.Dir(path)
pyvenvDir := filepath.Dir(cmdPath)
if !p.language.env.HasFilesInDir(pyvenvDir, "pyvenv.cfg") { if !p.language.env.HasFilesInDir(pyvenvDir, "pyvenv.cfg") {
pyvenvDir = filepath.Dir(pyvenvDir) pyvenvDir = filepath.Dir(pyvenvDir)
} }
if !p.language.env.HasFilesInDir(pyvenvDir, "pyvenv.cfg") { if !p.language.env.HasFilesInDir(pyvenvDir, "pyvenv.cfg") {
return "" return ""
} }
pyvenvCfg := p.env.FileContent(filepath.Join(pyvenvDir, "pyvenv.cfg")) pyvenvCfg := p.env.FileContent(filepath.Join(pyvenvDir, "pyvenv.cfg"))
for _, line := range strings.Split(pyvenvCfg, "\n") { for _, line := range strings.Split(pyvenvCfg, "\n") {
lineSplit := strings.SplitN(line, "=", 2) lineSplit := strings.SplitN(line, "=", 2)
@ -188,5 +199,6 @@ func (p *Python) pyvenvCfgPrompt() string {
return value return value
} }
} }
return "" return ""
} }

View file

@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
) )
// SaplingStatus represents part of the status of a Sapling repository // SaplingStatus represents part of the status of a Sapling repository
@ -85,7 +86,7 @@ func (sl *Sapling) shouldDisplay() bool {
sl.rootDir = slDir.Path sl.rootDir = slDir.Path
// convert the worktree file path to a windows one when in a WSL shared folder // convert the worktree file path to a windows one when in a WSL shared folder
sl.realDir = strings.TrimSuffix(sl.convertToWindowsPath(slDir.Path), "/.sl") sl.realDir = strings.TrimSuffix(sl.convertToWindowsPath(slDir.Path), "/.sl")
sl.RepoName = runtime.Base(sl.env, sl.convertToLinuxPath(sl.realDir)) sl.RepoName = path.Base(sl.convertToLinuxPath(sl.realDir))
sl.setDir(slDir.Path) sl.setDir(slDir.Path)
return true return true
@ -101,11 +102,13 @@ func (sl *Sapling) CacheKey() (string, bool) {
} }
func (sl *Sapling) setDir(dir string) { func (sl *Sapling) setDir(dir string) {
dir = runtime.ReplaceHomeDirPrefixWithTilde(sl.env, dir) // align with template PWD dir = path.ReplaceHomeDirPrefixWithTilde(dir) // align with template PWD
if sl.env.GOOS() == runtime.WINDOWS { if sl.env.GOOS() == runtime.WINDOWS {
sl.Dir = strings.TrimSuffix(dir, `\.sl`) sl.Dir = strings.TrimSuffix(dir, `\.sl`)
return return
} }
sl.Dir = strings.TrimSuffix(dir, "/.sl") sl.Dir = strings.TrimSuffix(dir, "/.sl")
} }

View file

@ -18,25 +18,13 @@ func TestSetDir(t *testing.T) {
GOOS string GOOS string
}{ }{
{ {
Case: "In home folder", Case: "Linux",
Expected: "~/sapling",
Path: "/usr/home/sapling/.sl",
GOOS: runtime.LINUX,
},
{
Case: "Outside home folder",
Expected: "/usr/sapling/repo", Expected: "/usr/sapling/repo",
Path: "/usr/sapling/repo/.sl", Path: "/usr/sapling/repo/.sl",
GOOS: runtime.LINUX, GOOS: runtime.LINUX,
}, },
{ {
Case: "Windows home folder", Case: "Windows",
Expected: "~\\sapling",
Path: "\\usr\\home\\sapling\\.sl",
GOOS: runtime.WINDOWS,
},
{
Case: "Windows outside home folder",
Expected: "\\usr\\sapling\\repo", Expected: "\\usr\\sapling\\repo",
Path: "\\usr\\sapling\\repo\\.sl", Path: "\\usr\\sapling\\repo\\.sl",
GOOS: runtime.WINDOWS, GOOS: runtime.WINDOWS,

View file

@ -8,6 +8,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -127,12 +128,6 @@ func TestSessionSegmentTemplate(t *testing.T) {
env.On("Getenv", "SSH_CLIENT").Return(SSHSession) env.On("Getenv", "SSH_CLIENT").Return(SSHSession)
env.On("Getenv", "POSH_SESSION_DEFAULT_USER").Return(tc.DefaultUserName) env.On("Getenv", "POSH_SESSION_DEFAULT_USER").Return(tc.DefaultUserName)
env.On("TemplateCache").Return(&cache.Template{
UserName: tc.UserName,
HostName: tc.ComputerName,
Root: tc.Root,
})
env.On("Platform").Return(tc.Platform) env.On("Platform").Return(tc.Platform)
var whoAmIErr error var whoAmIErr error
@ -145,6 +140,12 @@ func TestSessionSegmentTemplate(t *testing.T) {
session := &Session{} session := &Session{}
session.Init(properties.Map{}, env) session.Init(properties.Map{}, env)
template.Cache = &cache.Template{
UserName: tc.UserName,
HostName: tc.ComputerName,
Root: tc.Root,
}
_ = session.Enabled() _ = session.Enabled()
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, session), tc.Case) assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, session), tc.Case)
} }

View file

@ -5,11 +5,11 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestStatusWriterEnabled(t *testing.T) { func TestStatusWriterEnabled(t *testing.T) {
@ -27,18 +27,18 @@ func TestStatusWriterEnabled(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(mock.Environment) env := new(mock.Environment)
env.On("StatusCodes").Return(tc.Status, "") env.On("StatusCodes").Return(tc.Status, "")
env.On("TemplateCache").Return(&cache.Template{ env.On("Shell").Return(shell.GENERIC)
Code: 133,
})
env.On("Error", testify_.Anything).Return(nil)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
props := properties.Map{} props := properties.Map{}
if len(tc.Template) > 0 { if len(tc.Template) > 0 {
props[StatusTemplate] = tc.Template props[StatusTemplate] = tc.Template
} }
template.Cache = &cache.Template{
Code: 133,
}
template.Init(env, nil)
s := &Status{} s := &Status{}
s.Init(props, env) s.Init(props, env)

View file

@ -6,6 +6,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -29,19 +30,20 @@ func TestTextSegment(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(mock.Environment) env := new(mock.Environment)
env.On("PathSeparator").Return("/") env.On("PathSeparator").Return("/")
env.On("TemplateCache").Return(&cache.Template{
UserName: "Posh",
HostName: "MyHost",
Shell: "terminal",
Root: true,
Folder: "posh",
})
env.On("Getenv", "HELLO").Return("hello") env.On("Getenv", "HELLO").Return("hello")
env.On("Getenv", "WORLD").Return("") env.On("Getenv", "WORLD").Return("")
txt := &Text{} txt := &Text{}
txt.Init(properties.Map{}, env) txt.Init(properties.Map{}, env)
template.Cache = &cache.Template{
UserName: "Posh",
HostName: "MyHost",
Shell: "terminal",
Root: true,
Folder: "posh",
}
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, txt), tc.Case) assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, txt), tc.Case)
} }
} }

View file

@ -10,7 +10,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template" "github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
const ( const (
@ -93,10 +92,10 @@ func TestUI5Tooling(t *testing.T) {
tc.Template = ui5tooling.Template() tc.Template = ui5tooling.Template()
} }
// this is needed to build the version URL as before renderTemplate, the template is not initialized
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("TemplateCache").Return(&cache.Template{}) template.Cache = &cache.Template{}
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil) template.Init(env, nil)
template.Init(env)
failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
assert.True(t, ui5tooling.Enabled(), failMsg) assert.True(t, ui5tooling.Enabled(), failMsg)

View file

@ -9,6 +9,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
) )
const ( const (
@ -22,7 +23,7 @@ func getExecutablePath(env runtime.Environment) (string, error) {
} }
if env.Flags().Strict { if env.Flags().Strict {
return runtime.Base(env, executable), nil return path.Base(executable), nil
} }
// On Windows, it fails when the excutable is called in MSYS2 for example // On Windows, it fails when the excutable is called in MSYS2 for example

112
src/template/cache.go Normal file
View file

@ -0,0 +1,112 @@
package template
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
)
var (
Cache *cache.Template
)
func loadCache(vars maps.Simple) {
if !env.Flags().IsPrimary {
// Load the template cache for a non-primary prompt before rendering any templates.
if OK := restoreCache(env); OK {
return
}
}
Cache = new(cache.Template)
Cache.Root = env.Root()
Cache.Shell = env.Shell()
Cache.ShellVersion = env.Flags().ShellVersion
Cache.Code, _ = env.StatusCodes()
Cache.WSL = env.IsWsl()
Cache.Segments = maps.NewConcurrent()
Cache.PromptCount = env.Flags().PromptCount
Cache.Var = make(map[string]any)
Cache.Jobs = env.Flags().JobCount
if vars != nil {
Cache.Var = vars
}
pwd := env.Pwd()
Cache.PWD = path.ReplaceHomeDirPrefixWithTilde(pwd)
Cache.AbsolutePWD = pwd
if env.IsWsl() {
Cache.AbsolutePWD, _ = env.RunCommand("wslpath", "-m", pwd)
}
env.Flags().AbsolutePWD = Cache.AbsolutePWD
Cache.PSWD = env.Flags().PSWD
Cache.Folder = path.Base(pwd)
if env.GOOS() == runtime.WINDOWS && strings.HasSuffix(Cache.Folder, ":") {
Cache.Folder += `\`
}
Cache.UserName = env.User()
if host, err := env.Host(); err == nil {
Cache.HostName = host
}
goos := env.GOOS()
Cache.OS = goos
if goos == runtime.LINUX {
Cache.OS = env.Platform()
}
val := env.Getenv("SHLVL")
if shlvl, err := strconv.Atoi(val); err == nil {
Cache.SHLVL = shlvl
}
}
func restoreCache(env runtime.Environment) bool {
defer log.Trace(time.Now())
val, OK := env.Session().Get(cache.TEMPLATECACHE)
if !OK {
return false
}
var tmplCache cache.Template
err := json.Unmarshal([]byte(val), &tmplCache)
if err != nil {
log.Error(err)
return false
}
Cache = &tmplCache
Cache.Segments = Cache.SegmentsCache.ToConcurrent()
return true
}
func SaveCache() {
// only store this when in a primary prompt
// and when we have any extra prompt in the config
canSave := env.Flags().IsPrimary && env.Flags().HasExtra
if !canSave {
return
}
Cache.SegmentsCache = Cache.Segments.ToSimple()
templateCache, err := json.Marshal(Cache)
if err == nil {
env.Session().Set(cache.TEMPLATECACHE, string(templateCache), cache.ONEDAY)
}
}

View file

@ -7,7 +7,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestGlob(t *testing.T) { func TestGlob(t *testing.T) {
@ -23,12 +22,10 @@ func TestGlob(t *testing.T) {
} }
env := &mock.Environment{} env := &mock.Environment{}
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{})
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env) Cache = new(cache.Template)
Init(env, nil)
for _, tc := range cases { for _, tc := range cases {
tmpl := &Text{ tmpl := &Text{

View file

@ -3,6 +3,7 @@ package template
import ( import (
"sync" "sync"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
) )
@ -23,7 +24,7 @@ var (
knownVariables []string knownVariables []string
) )
func Init(environment runtime.Environment) { func Init(environment runtime.Environment, vars maps.Simple) {
env = environment env = environment
shell = env.Shell() shell = env.Shell()
@ -55,4 +56,10 @@ func Init(environment runtime.Environment) {
"Data", "Data",
"Jobs", "Jobs",
} }
if Cache != nil {
return
}
loadCache(vars)
} }

View file

@ -7,7 +7,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestUrl(t *testing.T) { func TestUrl(t *testing.T) {
@ -22,14 +21,11 @@ func TestUrl(t *testing.T) {
} }
env := &mock.Environment{} env := &mock.Environment{}
env.On("TemplateCache").Return(&cache.Template{})
env.On("Error", testify_.Anything)
env.On("Debug", testify_.Anything)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env) Cache = new(cache.Template)
Init(env, nil)
for _, tc := range cases { for _, tc := range cases {
tmpl := &Text{ tmpl := &Text{

View file

@ -8,6 +8,7 @@ import (
"text/template" "text/template"
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/log"
) )
type Data any type Data any
@ -21,7 +22,7 @@ type context struct {
func (c *context) init(t *Text) { func (c *context) init(t *Text) {
c.Data = t.Context c.Data = t.Context
c.Getenv = env.Getenv c.Getenv = env.Getenv
c.Template = *env.TemplateCache() c.Template = *Cache
} }
var renderPool sync.Pool var renderPool sync.Pool
@ -49,7 +50,7 @@ func (t *renderer) release() {
func (t *renderer) execute(text *Text) (string, error) { func (t *renderer) execute(text *Text) (string, error) {
tmpl, err := t.template.Parse(text.Template) tmpl, err := t.template.Parse(text.Template)
if err != nil { if err != nil {
env.Error(err) log.Error(err)
return "", errors.New(InvalidTemplate) return "", errors.New(InvalidTemplate)
} }
@ -57,7 +58,7 @@ func (t *renderer) execute(text *Text) (string, error) {
err = tmpl.Execute(&t.buffer, t.context) err = tmpl.Execute(&t.buffer, t.context)
if err != nil { if err != nil {
env.Error(err) log.Error(err)
return "", errors.New(IncorrectTemplate) return "", errors.New(IncorrectTemplate)
} }

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
) )
@ -15,7 +16,7 @@ type Text struct {
} }
func (t *Text) Render() (string, error) { func (t *Text) Render() (string, error) {
defer env.Trace(time.Now(), t.Template) defer log.Trace(time.Now(), t.Template)
if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") { if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") {
return t.Template, nil return t.Template, nil

View file

@ -5,11 +5,9 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/maps" "github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
) )
func TestRenderTemplate(t *testing.T) { func TestRenderTemplate(t *testing.T) {
@ -238,20 +236,16 @@ func TestRenderTemplateEnvVar(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
env := &mock.Environment{} env := &mock.Environment{}
env.On("Error", testify_.Anything)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{
OS: "darwin",
})
for k, v := range tc.Env { for k, v := range tc.Env {
env.On("Getenv", k).Return(v) env.On("Getenv", k).Return(v)
} }
Init(env) Cache = &cache.Template{
OS: "darwin",
}
Init(env, nil)
tmpl := &Text{ tmpl := &Text{
Template: tc.Template, Template: tc.Template,
@ -344,7 +338,7 @@ func TestPatchTemplate(t *testing.T) {
env := &mock.Environment{} env := &mock.Environment{}
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
Init(env) Init(env, nil)
for _, tc := range cases { for _, tc := range cases {
tmpl := &Text{ tmpl := &Text{
@ -370,14 +364,12 @@ func TestSegmentContains(t *testing.T) {
env := &mock.Environment{} env := &mock.Environment{}
segments := maps.NewConcurrent() segments := maps.NewConcurrent()
segments.Set("Git", "foo") segments.Set("Git", "foo")
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{
Segments: segments,
})
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env) Cache = &cache.Template{
Segments: segments,
}
Init(env, nil)
for _, tc := range cases { for _, tc := range cases {
tmpl := &Text{ tmpl := &Text{