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

View file

@ -5,7 +5,7 @@ import (
"os"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"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] {
case "edit":
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
},
}
env.ResolveConfigPath()
os.Exit(editFileWithEditor(env.CmdFlags.Config))
path := config.Path((configFlag))
os.Exit(editFileWithEditor(path))
case "get":
// only here for backwards compatibility
fmt.Print(time.Now().UnixNano() / 1000000)

View file

@ -7,7 +7,8 @@ import (
"strings"
"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"
)
@ -39,14 +40,7 @@ Exports the current config to "~/new_config.omp.json" (in JSON format).`,
os.Exit(2)
}
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
Config: configFlag,
},
}
env.Init()
defer env.Close()
cfg := config.Load(env)
cfg := config.Load(configFlag, shell.GENERIC, false)
validateExportFormat := func() {
format = strings.ToLower(format)
@ -74,7 +68,7 @@ Exports the current config to "~/new_config.omp.json" (in JSON format).`,
return
}
cfg.Output = cleanOutputPath(output, env)
cfg.Output = cleanOutputPath(output)
if len(format) == 0 {
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 {
path = runtime.ReplaceTildePrefixWithHomeDir(env, path)
func cleanOutputPath(output string) string {
output = path.ReplaceTildePrefixWithHomeDir(output)
if !filepath.IsAbs(path) {
if absPath, err := filepath.Abs(path); err == nil {
path = absPath
if !filepath.IsAbs(output) {
if absPath, err := filepath.Abs(output); err == nil {
output = absPath
}
}
return filepath.Clean(path)
return filepath.Clean(output)
}
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.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
cfg := config.Load(configFlag, shell.GENERIC, false)
flags := &runtime.Flags{
Config: configFlag,
Shell: shell.GENERIC,
TerminalWidth: 150,
},
}
env.Init()
defer env.Close()
env := &runtime.Terminal{}
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
cfg.ConsoleTitleTemplate = ""
cfg.PWD = ""
// add variables to the environment
env.Var = cfg.Var
terminal.Init(shell.GENERIC)
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors()
terminal.Colors = cfg.MakeColors(env)
eng := &prompt.Engine{
Config: cfg,
@ -90,7 +90,7 @@ Exports the config to an image file using customized output options.`,
}
if outputImage != "" {
imageCreator.Path = cleanOutputPath(outputImage, env)
imageCreator.Path = cleanOutputPath(outputImage)
}
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/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"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.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
cfg := config.Load(configFlag, shell.GENERIC, true)
flags := &runtime.Flags{
Config: configFlag,
Migrate: true,
},
}
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close()
cfg := config.Load(env)
if write {
cfg.BackupAndMigrate()
return

View file

@ -5,6 +5,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"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.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
cfg := config.Load(configFlag, shell.GENERIC, false)
flags := &runtime.Flags{
Config: configFlag,
},
}
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close()
cfg := config.Load(env)
cfg.MigrateGlyphs = true
if len(format) == 0 {

View file

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

View file

@ -43,14 +43,13 @@ func init() {
}
func toggleFeature(cmd *cobra.Command, feature string, enable bool) {
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
Shell: shellName,
SaveCache: true,
},
}
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close()
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]
}
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
SaveCache: true,
},
}
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close()
terminal.Init(env.Shell())

View file

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

View file

@ -70,23 +70,26 @@ func runInit(sh string) {
startTime = time.Now()
}
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
cfg := config.Load(configFlag, sh, false)
flags := &runtime.Flags{
Shell: sh,
Config: configFlag,
Strict: strict,
Debug: debug,
},
}
env.Init()
defer env.Close()
env := &runtime.Terminal{}
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

View file

@ -15,13 +15,12 @@ var noticeCmd = &cobra.Command{
Long: "Print the upgrade notice when a new version is available.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
env := &runtime.Terminal{
CmdFlags: &runtime.Flags{
flags := &runtime.Flags{
SaveCache: true,
},
}
env.Init()
env := &runtime.Terminal{}
env.Init(flags)
defer env.Close()
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/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
)
@ -81,7 +82,11 @@ func createPrintCmd() *cobra.Command {
}
eng := prompt.New(flags)
defer eng.Env.Close()
defer func() {
template.SaveCache()
eng.Env.Close()
}()
switch args[0] {
case prompt.DEBUG:

View file

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

View file

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

View file

@ -81,13 +81,11 @@ func TestAnsiRender(t *testing.T) {
for _, tc := range cases {
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("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}}")
got := ansi.ResolveTemplate()

View file

@ -22,7 +22,6 @@ const (
// Config holds all the theme for rendering the prompt
type Config struct {
env runtime.Environment
Palette color.Palette `json:"palette,omitempty" toml:"palette,omitempty"`
DebugPrompt *Segment `json:"debug_prompt,omitempty" toml:"debug_prompt,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"`
}
func (cfg *Config) MakeColors() color.String {
cacheDisabled := cfg.env.Getenv("OMP_CACHE_DISABLED") == "1"
return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, cfg.env)
func (cfg *Config) MakeColors(env runtime.Environment) color.String {
cacheDisabled := env.Getenv("OMP_CACHE_DISABLED") == "1"
return color.MakeColors(cfg.getPalette(), !cacheDisabled, cfg.AccentColor, env)
}
func (cfg *Config) getPalette() color.Palette {
@ -88,7 +87,7 @@ func (cfg *Config) getPalette() color.Palette {
return palette
}
func (cfg *Config) Features() shell.Features {
func (cfg *Config) Features(env runtime.Environment) shell.Features {
var feats shell.Features
if cfg.TransientPrompt != nil {
@ -100,12 +99,12 @@ func (cfg *Config) Features() shell.Features {
}
autoUpgrade := cfg.AutoUpgrade
if _, OK := cfg.env.Cache().Get(AUTOUPGRADE); OK {
if _, OK := env.Cache().Get(AUTOUPGRADE); OK {
autoUpgrade = true
}
upgradeNotice := cfg.UpgradeNotice
if _, OK := cfg.env.Cache().Get(UPGRADENOTICE); OK {
if _, OK := env.Cache().Get(UPGRADENOTICE); OK {
upgradeNotice = true
}
@ -125,7 +124,7 @@ func (cfg *Config) Features() shell.Features {
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)
}

View file

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

View file

@ -3,11 +3,10 @@ package config
import (
"github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"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 }}"
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
}

View file

@ -3,13 +3,17 @@ package config
import (
"bytes"
"fmt"
"os"
stdOS "os"
"path/filepath"
"runtime"
"strings"
"time"
"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"
json "github.com/goccy/go-json"
@ -18,11 +22,15 @@ import (
)
// LoadConfig returns the default configuration including possible user overrides
func Load(env runtime.Environment) *Config {
cfg := loadConfig(env)
func Load(configFile, sh string, migrate bool) *Config {
defer log.Trace(time.Now())
configFile = Path(configFile)
cfg := loadConfig(configFile)
// only migrate automatically when the switch isn't set
if !env.Flags().Migrate && cfg.Version < Version {
if !migrate && cfg.Version < Version {
cfg.BackupAndMigrate()
}
@ -39,7 +47,7 @@ func Load(env runtime.Environment) *Config {
// elv - broken OSC sequences
// xonsh - broken OSC sequences
// 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:
cfg.ShellIntegration = false
}
@ -47,24 +55,71 @@ func Load(env runtime.Environment) *Config {
return cfg
}
func loadConfig(env runtime.Environment) *Config {
defer env.Trace(time.Now())
configFile := env.Flags().Config
func Path(config string) string {
defer log.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
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 {
env.Debug("no config file specified, using default")
return Default(env, false)
log.Debug("no config file specified, using default")
return Default(false)
}
var cfg Config
cfg.origin = configFile
cfg.Format = strings.TrimPrefix(filepath.Ext(configFile), ".")
cfg.env = env
data, err := stdOS.ReadFile(configFile)
if err != nil {
env.Error(err)
return Default(env, true)
log.Error(err)
return Default(true)
}
switch cfg.Format {
@ -87,8 +142,8 @@ func loadConfig(env runtime.Environment) *Config {
}
if err != nil {
env.Error(err)
return Default(env, true)
log.Error(err)
return Default(true)
}
return &cfg

View file

@ -121,7 +121,7 @@ func (segment *Segment) Execute(env runtime.Environment) {
if segment.writer.Enabled() {
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
if !segment.Enabled {
segment.env.TemplateCache().RemoveSegmentData(segment.Name())
template.Cache.RemoveSegmentData(segment.Name())
return
}
@ -142,7 +142,7 @@ func (segment *Segment) Render() {
segment.setCache()
// 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 {
@ -231,7 +231,7 @@ func (segment *Segment) restoreCache() bool {
}
segment.Enabled = true
segment.env.TemplateCache().AddSegmentData(segment.Name(), segment.writer)
template.Cache.AddSegmentData(segment.Name(), segment.writer)
return true
}

View file

@ -35,6 +35,7 @@ import (
"strings"
"unicode/utf8"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
fontCLI "github.com/jandedobbeleer/oh-my-posh/src/font"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
@ -218,7 +219,7 @@ func (ir *Renderer) setOutputPath(config string) {
func (ir *Renderer) loadFonts() error {
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 {
data, _ = stdOS.ReadFile(fontCachePath)
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"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("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
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
// of the prompt components.
func New(flags *runtime.Flags) *Engine {
env := &runtime.Terminal{
CmdFlags: flags,
}
cfg := config.Load(flags.Config, flags.Shell, flags.Migrate)
env.Init()
cfg := config.Load(env)
env.Var = cfg.Var
env := &runtime.Terminal{}
env.Init(flags)
// To prevent cross-segment template referencing issues, this should not be moved elsewhere.
// Related: https://github.com/JanDeDobbeleer/oh-my-posh/discussions/2885#discussioncomment-4497439
env.PopulateTemplateCache()
template.Init(env)
template.Init(env, cfg.Var)
flags.HasExtra = cfg.DebugPrompt != nil ||
cfg.SecondaryPrompt != nil ||
@ -484,7 +477,7 @@ func New(flags *runtime.Flags) *Engine {
terminal.Init(env.Shell())
terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate()
terminal.Colors = cfg.MakeColors()
terminal.Colors = cfg.MakeColors(env)
terminal.Plain = flags.Plain
eng := &Engine{

View file

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

View file

@ -33,42 +33,39 @@ type Environment interface {
Shell() string
Platform() string
StatusCodes() (int, string)
PathSeparator() string
HasFiles(pattern string) bool
HasFilesInDir(dir, pattern 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
ResolveSymlink(path string) (string, error)
ResolveSymlink(input string) (string, error)
DirMatchesOneOf(dir string, regexes []string) bool
DirIsWritable(path string) bool
DirIsWritable(input string) bool
CommandPath(command string) string
HasCommand(command string) bool
FileContent(file string) string
LsDir(path string) []fs.DirEntry
LsDir(input string) []fs.DirEntry
RunCommand(command string, args ...string) (string, error)
RunShellCommand(shell, command string) string
ExecutionTime() float64
Flags() *Flags
BatteryState() (*battery.Info, 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)
IsWsl() bool
IsWsl2() bool
IsCygwin() bool
StackCount() int
TerminalWidth() (int, error)
CachePath() string
Cache() cache.Cache
Session() cache.Cache
Close()
Logs() string
InWSLSharedDrive() bool
ConvertToLinuxPath(path string) string
ConvertToWindowsPath(path string) string
ConvertToLinuxPath(input string) string
ConvertToWindowsPath(input string) string
Connection(connectionType ConnectionType) (*Connection, error)
TemplateCache() *cache.Template
CursorPosition() (row, col int)
SystemInfo() (*SystemInfo, error)
Debug(message string)
@ -84,6 +81,7 @@ type Flags struct {
Shell string
ShellVersion string
PWD string
AbsolutePWD string
Type string
ErrorCode int
PromptCount int

View file

@ -47,8 +47,8 @@ func (env *Environment) HasFolder(folder string) bool {
return args.Bool(0)
}
func (env *Environment) ResolveSymlink(path string) (string, error) {
args := env.Called(path)
func (env *Environment) ResolveSymlink(input string) (string, error) {
args := env.Called(input)
return args.String(0), args.Error(1)
}
@ -57,16 +57,11 @@ func (env *Environment) FileContent(file string) string {
return args.String(0)
}
func (env *Environment) LsDir(path string) []fs.DirEntry {
args := env.Called(path)
func (env *Environment) LsDir(input string) []fs.DirEntry {
args := env.Called(input)
return args.Get(0).([]fs.DirEntry)
}
func (env *Environment) PathSeparator() string {
args := env.Called()
return args.String(0)
}
func (env *Environment) User() string {
args := env.Called()
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)
}
func (env *Environment) TemplateCache() *cache.Template {
args := env.Called()
return args.Get(0).(*cache.Template)
}
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...)
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 (
"context"
"encoding/json"
"errors"
"fmt"
"io"
@ -22,8 +21,8 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/maps"
"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/config"
"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"
load "github.com/shirou/gopsutil/v3/load"
@ -32,20 +31,20 @@ import (
type Terminal struct {
CmdFlags *Flags
Var maps.Simple
cmdCache *cache.Command
deviceCache *cache.File
sessionCache *cache.File
tmplCache *cache.Template
lsDirMap maps.Concurrent
cwd string
host string
networks []*Connection
}
func (term *Terminal) Init() {
func (term *Terminal) Init(flags *Flags) {
defer term.Trace(time.Now())
term.CmdFlags = flags
if term.CmdFlags == nil {
term.CmdFlags = &Flags{}
}
@ -61,9 +60,9 @@ func (term *Terminal) Init() {
}
initCache := func(fileName string) *cache.File {
cache := &cache.File{}
cache.Init(filepath.Join(term.CachePath(), fileName), term.CmdFlags.SaveCache)
return cache
fileCache := &cache.File{}
fileCache.Init(filepath.Join(cache.Path(), fileName), term.CmdFlags.SaveCache)
return fileCache
}
term.deviceCache = initCache(cache.FileName)
@ -72,67 +71,9 @@ func (term *Terminal) Init() {
term.setPwd()
term.ResolveConfigPath()
term.cmdCache = &cache.Command{
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) {
@ -179,7 +120,7 @@ func (term *Terminal) setPwd() {
}
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)
return
}
@ -277,9 +218,9 @@ func (term *Terminal) HasFolder(folder string) bool {
return isDir
}
func (term *Terminal) ResolveSymlink(path string) (string, error) {
defer term.Trace(time.Now(), path)
link, err := filepath.EvalSymlinks(path)
func (term *Terminal) ResolveSymlink(input string) (string, error) {
defer term.Trace(time.Now(), input)
link, err := filepath.EvalSymlinks(input)
if err != nil {
term.Error(err)
return "", err
@ -293,35 +234,32 @@ func (term *Terminal) FileContent(file string) string {
if !filepath.IsAbs(file) {
file = filepath.Join(term.Pwd(), file)
}
content, err := os.ReadFile(file)
if err != nil {
term.Error(err)
return ""
}
fileContent := string(content)
term.Debug(fileContent)
return fileContent
}
func (term *Terminal) LsDir(path string) []fs.DirEntry {
defer term.Trace(time.Now(), path)
entries, err := os.ReadDir(path)
func (term *Terminal) LsDir(input string) []fs.DirEntry {
defer term.Trace(time.Now(), input)
entries, err := os.ReadDir(input)
if err != nil {
term.Error(err)
return nil
}
term.DebugF("%v", entries)
return entries
}
func (term *Terminal) PathSeparator() string {
defer term.Trace(time.Now())
if term.GOOS() == WINDOWS {
return `\`
}
return "/"
}
func (term *Terminal) User() string {
defer term.Trace(time.Now())
user := os.Getenv("USER")
@ -356,6 +294,10 @@ func (term *Terminal) GOOS() string {
return runtime.GOOS
}
func (term *Terminal) Home() string {
return path.Home()
}
func (term *Terminal) RunCommand(command string, args ...string) (string, error) {
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 {
defer term.Trace(time.Now(), command)
if path, ok := term.cmdCache.Get(command); ok {
term.Debug(path)
return path
if cmdPath, ok := term.cmdCache.Get(command); ok {
term.Debug(cmdPath)
return cmdPath
}
path, err := exec.LookPath(command)
cmdPath, err := exec.LookPath(command)
if err == nil {
term.cmdCache.Set(command, path)
term.Debug(path)
return path
term.cmdCache.Set(command, cmdPath)
term.Debug(cmdPath)
return cmdPath
}
term.Error(err)
@ -402,9 +344,11 @@ func (term *Terminal) CommandPath(command string) string {
func (term *Terminal) HasCommand(command string) bool {
defer term.Trace(time.Now(), command)
if path := term.CommandPath(command); path != "" {
if cmdPath := term.CommandPath(command); cmdPath != "" {
return true
}
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) {
defer term.Trace(time.Now(), parent)
path := term.Pwd()
pwd := term.Pwd()
if followSymlinks {
if actual, err := term.ResolveSymlink(path); err == nil {
path = actual
if actual, err := term.ResolveSymlink(pwd); err == nil {
pwd = actual
}
}
for {
fileSystem := os.DirFS(path)
fileSystem := os.DirFS(pwd)
info, err := fs.Stat(fileSystem, parent)
if err == nil {
return &FileInfo{
ParentFolder: path,
Path: filepath.Join(path, parent),
ParentFolder: pwd,
Path: filepath.Join(pwd, parent),
IsDir: info.IsDir(),
}, nil
}
@ -551,8 +495,8 @@ func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*Fi
return nil, err
}
if dir := filepath.Dir(path); dir != path {
path = dir
if dir := filepath.Dir(pwd); dir != pwd {
pwd = dir
continue
}
@ -563,6 +507,7 @@ func (term *Terminal) HasParentFilePath(parent string, followSymlinks bool) (*Fi
func (term *Terminal) StackCount() int {
defer term.Trace(time.Now())
if term.CmdFlags.StackCount < 0 {
return 0
}
@ -578,26 +523,8 @@ func (term *Terminal) Session() cache.Cache {
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() {
defer term.Trace(time.Now())
term.saveTemplateCache()
term.clearCacheFiles()
term.deviceCache.Close()
term.sessionCache.Close()
@ -608,7 +535,7 @@ func (term *Terminal) clearCacheFiles() {
return
}
deletedFiles, err := cache.Clear(term.CachePath(), false)
deletedFiles, err := cache.Clear(cache.Path(), false)
if err != nil {
term.Error(err)
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 {
return log.String()
}
func (term *Terminal) TemplateCache() *cache.Template {
return term.tmplCache
}
func (term *Terminal) DirMatchesOneOf(dir string, regexes []string) (match bool) {
// 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
@ -797,170 +646,6 @@ func (term *Terminal) SystemInfo() (*SystemInfo, error) {
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 {
garbage := []string{
".lan",

View file

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

View file

@ -3,12 +3,12 @@ package runtime
import (
"errors"
"fmt"
"os"
"strings"
"syscall"
"time"
"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/registry"
)
@ -50,22 +50,6 @@ func (term *Terminal) Root() bool {
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) {
defer term.Trace(time.Now(), 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.
//
// Returns a variant type if successful; nil and an error if not.
func (term *Terminal) WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error) {
term.Trace(time.Now(), path)
func (term *Terminal) WindowsRegistryKeyValue(input string) (*WindowsRegistryValue, error) {
term.Trace(time.Now(), input)
// Format:
// "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.
//
rootKey, regPath, found := strings.Cut(path, `\`)
rootKey, regPath, found := strings.Cut(input, `\`)
if !found {
err := fmt.Errorf("Error, malformed registry path: '%s'", path)
err := fmt.Errorf("Error, malformed registry path: '%s'", input)
term.Error(err)
return nil, err
}
var regKey string
if !strings.HasSuffix(regPath, `\`) {
regKey = Base(term, regPath)
regKey = path.Base(regPath)
if len(regKey) != 0 {
regPath = strings.TrimSuffix(regPath, `\`+regKey)
}
@ -218,17 +202,17 @@ func (term *Terminal) InWSLSharedDrive() bool {
return false
}
func (term *Terminal) ConvertToWindowsPath(path string) string {
return strings.ReplaceAll(path, `\`, "/")
func (term *Terminal) ConvertToWindowsPath(input string) string {
return strings.ReplaceAll(input, `\`, "/")
}
func (term *Terminal) ConvertToLinuxPath(path string) string {
return path
func (term *Terminal) ConvertToLinuxPath(input string) string {
return input
}
func (term *Terminal) DirIsWritable(path string) bool {
func (term *Terminal) DirIsWritable(input string) bool {
defer term.Trace(time.Now())
return term.isWriteable(path)
return term.isWriteable(input)
}
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/regex"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"gopkg.in/ini.v1"
)
@ -365,8 +366,8 @@ func (g *Git) getBareRepoInfo() {
// we can still have a pointer to a bare repo
if file, err := g.env.HasParentFilePath(".git", true); err == nil && !file.IsDir {
content := g.FileContents(file.ParentFolder, ".git")
path := strings.TrimPrefix(content, "gitdir: ")
g.workingDir = filepath.Join(file.ParentFolder, path)
dir := strings.TrimPrefix(content, "gitdir: ")
g.workingDir = filepath.Join(file.ParentFolder, dir)
}
head := g.FileContents(g.workingDir, "HEAD")
@ -384,7 +385,7 @@ func (g *Git) getBareRepoInfo() {
}
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 {
g.Dir = strings.TrimSuffix(dir, `\.git`)
return
@ -516,9 +517,9 @@ func (g *Git) cleanUpstreamURL(url string) string {
}
if len(match) != 0 {
path := strings.Trim(match["PATH"], "/")
path = strings.TrimSuffix(path, ".git")
return fmt.Sprintf("https://%s/%s", match["URL"], path)
repoPath := strings.Trim(match["PATH"], "/")
repoPath = strings.TrimSuffix(repoPath, ".git")
return fmt.Sprintf("https://%s/%s", match["URL"], repoPath)
}
// 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 {
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")
if ind > -1 {
return runtime.Base(g.env, g.workingDir[:ind])
return path.Base(g.workingDir[:ind])
}
return ""

View file

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

View file

@ -7,9 +7,11 @@ func resolveGitPath(base, path string) string {
if len(path) == 0 {
return base
}
if filepath.IsAbs(path) {
return path
}
// Note that git on Windows uses slashes exclusively. And it's okay
// because Windows actually accepts both directory separators. More
// 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.
return filepath.VolumeName(base) + path
}
return filepath.ToSlash(filepath.Join(base, path))
}

View file

@ -4,6 +4,7 @@ import (
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
)
const (
@ -90,7 +91,7 @@ func (hg *Mercurial) shouldDisplay() bool {
}
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 {
hg.Dir = strings.TrimSuffix(dir, `\.hg`)
return

View file

@ -6,6 +6,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
@ -89,9 +90,6 @@ func TestOSInfo(t *testing.T) {
env := new(mock.Environment)
env.On("GOOS").Return(tc.GOOS)
env.On("Platform").Return(tc.Platform)
env.On("TemplateCache").Return(&cache.Template{
WSL: tc.IsWSL,
})
props := properties.Map{
DisplayDistroName: tc.DisplayDistroName,
@ -106,6 +104,10 @@ func TestOSInfo(t *testing.T) {
osInfo := &Os{}
osInfo.Init(props, env)
template.Cache = &cache.Template{
WSL: tc.IsWSL,
}
_ = osInfo.Enabled()
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/regex"
"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/template"
)
@ -126,7 +127,7 @@ func (pt *Path) Enabled() bool {
pt.setStyle()
pwd := pt.env.Pwd()
pt.Location = pt.env.TemplateCache().AbsolutePWD
pt.Location = pt.env.Flags().AbsolutePWD
if pt.env.GOOS() == runtime.WINDOWS {
pt.Location = strings.ReplaceAll(pt.Location, `\`, `/`)
}
@ -152,7 +153,7 @@ func (pt *Path) setPaths() {
pt.cygPath = displayCygpath()
pt.windowsPath = pt.env.GOOS() == runtime.WINDOWS && !pt.cygPath
pt.pathSeparator = pt.env.PathSeparator()
pt.pathSeparator = path.Separator()
pt.pwd = pt.env.Pwd()
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) {
sb.WriteString(folderSeparator)
}
for _, folder := range folders[:len(folders)-1] {
sb.WriteString(folder)
sb.WriteString(folderSeparator)
}
return sb.String()
}
@ -267,6 +270,7 @@ func (pt *Path) getFolderSeparator() string {
if len(separator) == 0 {
return pt.pathSeparator
}
return separator
}
@ -568,21 +572,21 @@ func (pt *Path) setMappedLocations() {
Context: pt,
}
path, err := tmpl.Render()
location, err := tmpl.Render()
if err != nil {
pt.env.Error(err)
}
if len(path) == 0 {
if len(location) == 0 {
continue
}
// 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
}
mappedLocations[pt.normalize(path)] = value
mappedLocations[pt.normalize(location)] = value
}
pt.mappedLocations = mappedLocations
@ -660,9 +664,9 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
}
if pt.cygPath {
path, err := pt.env.RunCommand("cygpath", "-u", inputPath)
if len(path) != 0 {
inputPath = path
cygPath, err := pt.env.RunCommand("cygpath", "-u", inputPath)
if len(cygPath) != 0 {
inputPath = cygPath
pt.pathSeparator = "/"
}
@ -697,25 +701,26 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
return root, relative
}
func (pt *Path) isRootFS(path string) bool {
return len(path) == 1 && runtime.IsPathSeparator(pt.env, path[0])
func (pt *Path) isRootFS(inputPath string) bool {
return len(inputPath) == 1 && path.IsSeparator(inputPath[0])
}
func (pt *Path) endWithSeparator(path string) bool {
if len(path) == 0 {
func (pt *Path) endWithSeparator(inputPath string) bool {
if len(inputPath) == 0 {
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 {
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 = runtime.CleanPath(pt.env, normalized)
normalized = path.Clean(normalized)
if pt.env.GOOS() == runtime.WINDOWS || pt.env.GOOS() == runtime.DARWIN {
normalized = strings.ToLower(normalized)
@ -827,9 +832,9 @@ func (pt *Path) makeFolderFormatMap() map[string]string {
if gitDirFormat := pt.props.GetString(GitDirFormat, ""); len(gitDirFormat) != 0 {
dir, err := pt.env.HasParentFilePath(".git", false)
if err == nil && dir.IsDir {
// Make it consistent with the modified path.
path := pt.join(pt.replaceMappedLocations(dir.ParentFolder))
folderFormatMap[path] = gitDirFormat
// Make it consistent with the modified parent.
parent := pt.join(pt.replaceMappedLocations(dir.ParentFolder))
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"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"gopkg.in/yaml.v3"
)
@ -135,10 +136,15 @@ func (p *Pulumi) getProjectName() error {
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()
_, err := h.Write([]byte(value))
_, err := h.Write([]byte(s))
if err != nil {
p.env.Error(err)
return ""
@ -147,11 +153,6 @@ func (p *Pulumi) getProjectName() error {
return hex.EncodeToString(h.Sum(nil))
}
p.workspaceSHA1 = sha1HexString(p.env.Pwd() + p.env.PathSeparator() + fileName)
return nil
}
func (p *Pulumi) getPulumiAbout() {
if len(p.Stack) == 0 {
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 (
"errors"
"fmt"
"path/filepath"
"testing"
"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/path"
"github.com/stretchr/testify/assert"
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{"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("Error", 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("FileContent", pulumiJSON).Return(tc.JSONConfig, nil)
env.On("PathSeparator").Return("/")
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("FileContent", filepath.Clean("/home/foobar/.pulumi/workspaces/"+workspaceFile)).Return(tc.WorkSpaceFile, nil)
props := properties.Map{
@ -185,7 +199,6 @@ description: A Console App
FetchAbout: tc.FetchAbout,
}
pulumi := &Pulumi{}
pulumi.Init(props, env)
assert.Equal(t, tc.ExpectedEnabled, pulumi.Enabled(), tc.Case)

View file

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

View file

@ -4,6 +4,7 @@ import (
"strings"
"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
@ -85,7 +86,7 @@ func (sl *Sapling) shouldDisplay() bool {
sl.rootDir = slDir.Path
// 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.RepoName = runtime.Base(sl.env, sl.convertToLinuxPath(sl.realDir))
sl.RepoName = path.Base(sl.convertToLinuxPath(sl.realDir))
sl.setDir(slDir.Path)
return true
@ -101,11 +102,13 @@ func (sl *Sapling) CacheKey() (string, bool) {
}
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 {
sl.Dir = strings.TrimSuffix(dir, `\.sl`)
return
}
sl.Dir = strings.TrimSuffix(dir, "/.sl")
}

View file

@ -18,25 +18,13 @@ func TestSetDir(t *testing.T) {
GOOS string
}{
{
Case: "In home folder",
Expected: "~/sapling",
Path: "/usr/home/sapling/.sl",
GOOS: runtime.LINUX,
},
{
Case: "Outside home folder",
Case: "Linux",
Expected: "/usr/sapling/repo",
Path: "/usr/sapling/repo/.sl",
GOOS: runtime.LINUX,
},
{
Case: "Windows home folder",
Expected: "~\\sapling",
Path: "\\usr\\home\\sapling\\.sl",
GOOS: runtime.WINDOWS,
},
{
Case: "Windows outside home folder",
Case: "Windows",
Expected: "\\usr\\sapling\\repo",
Path: "\\usr\\sapling\\repo\\.sl",
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/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
@ -127,12 +128,6 @@ func TestSessionSegmentTemplate(t *testing.T) {
env.On("Getenv", "SSH_CLIENT").Return(SSHSession)
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)
var whoAmIErr error
@ -145,6 +140,12 @@ func TestSessionSegmentTemplate(t *testing.T) {
session := &Session{}
session.Init(properties.Map{}, env)
template.Cache = &cache.Template{
UserName: tc.UserName,
HostName: tc.ComputerName,
Root: tc.Root,
}
_ = session.Enabled()
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/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"
testify_ "github.com/stretchr/testify/mock"
)
func TestStatusWriterEnabled(t *testing.T) {
@ -27,18 +27,18 @@ func TestStatusWriterEnabled(t *testing.T) {
for _, tc := range cases {
env := new(mock.Environment)
env.On("StatusCodes").Return(tc.Status, "")
env.On("TemplateCache").Return(&cache.Template{
Code: 133,
})
env.On("Error", testify_.Anything).Return(nil)
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
env.On("Flags").Return(&runtime.Flags{})
env.On("Shell").Return(shell.GENERIC)
props := properties.Map{}
if len(tc.Template) > 0 {
props[StatusTemplate] = tc.Template
}
template.Cache = &cache.Template{
Code: 133,
}
template.Init(env, nil)
s := &Status{}
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/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
@ -29,19 +30,20 @@ func TestTextSegment(t *testing.T) {
for _, tc := range cases {
env := new(mock.Environment)
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", "WORLD").Return("")
txt := &Text{}
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)
}
}

View file

@ -10,7 +10,6 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
)
const (
@ -93,10 +92,10 @@ func TestUI5Tooling(t *testing.T) {
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("TemplateCache").Return(&cache.Template{})
env.On("Trace", testify_.Anything, testify_.Anything).Return(nil)
template.Init(env)
template.Cache = &cache.Template{}
template.Init(env, nil)
failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
assert.True(t, ui5tooling.Enabled(), failMsg)

View file

@ -9,6 +9,7 @@ import (
"github.com/google/uuid"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
)
const (
@ -22,7 +23,7 @@ func getExecutablePath(env runtime.Environment) (string, error) {
}
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

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/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestGlob(t *testing.T) {
@ -23,12 +22,10 @@ func TestGlob(t *testing.T) {
}
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("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env)
Cache = new(cache.Template)
Init(env, nil)
for _, tc := range cases {
tmpl := &Text{

View file

@ -3,6 +3,7 @@ package template
import (
"sync"
"github.com/jandedobbeleer/oh-my-posh/src/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
)
@ -23,7 +24,7 @@ var (
knownVariables []string
)
func Init(environment runtime.Environment) {
func Init(environment runtime.Environment, vars maps.Simple) {
env = environment
shell = env.Shell()
@ -55,4 +56,10 @@ func Init(environment runtime.Environment) {
"Data",
"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/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestUrl(t *testing.T) {
@ -22,14 +21,11 @@ func TestUrl(t *testing.T) {
}
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("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env)
Cache = new(cache.Template)
Init(env, nil)
for _, tc := range cases {
tmpl := &Text{

View file

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

View file

@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
)
@ -15,7 +16,7 @@ type Text struct {
}
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, "}}") {
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/maps"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/stretchr/testify/assert"
testify_ "github.com/stretchr/testify/mock"
)
func TestRenderTemplate(t *testing.T) {
@ -238,20 +236,16 @@ func TestRenderTemplateEnvVar(t *testing.T) {
}
for _, tc := range cases {
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("Trace", testify_.Anything, testify_.Anything).Return(nil)
env.On("TemplateCache").Return(&cache.Template{
OS: "darwin",
})
for k, v := range tc.Env {
env.On("Getenv", k).Return(v)
}
Init(env)
Cache = &cache.Template{
OS: "darwin",
}
Init(env, nil)
tmpl := &Text{
Template: tc.Template,
@ -344,7 +338,7 @@ func TestPatchTemplate(t *testing.T) {
env := &mock.Environment{}
env.On("Shell").Return("foo")
Init(env)
Init(env, nil)
for _, tc := range cases {
tmpl := &Text{
@ -370,14 +364,12 @@ func TestSegmentContains(t *testing.T) {
env := &mock.Environment{}
segments := maps.NewConcurrent()
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("Trace", testify_.Anything, testify_.Anything).Return(nil)
Init(env)
Cache = &cache.Template{
Segments: segments,
}
Init(env, nil)
for _, tc := range cases {
tmpl := &Text{