diff --git a/src/engine/block.go b/src/engine/block.go index 618c088e..83c37e31 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -22,7 +22,7 @@ const ( Prompt BlockType = "prompt" // LineBreak creates a line break in the prompt LineBreak BlockType = "newline" - // RPrompt a right aligned prompt in ZSH and Powershell + // RPrompt is a right aligned prompt RPrompt BlockType = "rprompt" // Left aligns left Left BlockAlignment = "left" diff --git a/src/engine/engine.go b/src/engine/engine.go index 8b11a63f..4d0d88d4 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -130,7 +130,7 @@ func (e *Engine) shouldFill(block *Block, length int) (string, bool) { func (e *Engine) renderBlock(block *Block) { defer func() { - // Due to a bug in Powershell, the end of the line needs to be cleared. + // Due to a bug in PowerShell, the end of the line needs to be cleared. // If this doesn't happen, the portion after the prompt gets colored in the background // color of the line above the new input line. Clearing the line fixes this, // but can hopefully one day be removed when this is resolved natively. diff --git a/src/environment/shell.go b/src/environment/shell.go index a31bb717..b1d2763f 100644 --- a/src/environment/shell.go +++ b/src/environment/shell.go @@ -337,11 +337,16 @@ func (env *ShellEnvironment) Pwd() string { } correctPath := func(pwd string) string { // on Windows, and being case sensitive and not consistent and all, this gives silly issues - driveLetter := regex.GetCompiledRegex(`^[a-z]:`) - return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper) + if env.GOOS() == WINDOWS { + driveLetter := regex.GetCompiledRegex(`^[a-z]:`) + return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper) + } + return pwd } if env.CmdFlags != nil && env.CmdFlags.PWD != "" { - env.cwd = correctPath(env.CmdFlags.PWD) + // ensure a clean path + root, path := ParsePath(env, correctPath(env.CmdFlags.PWD)) + env.cwd = root + path return env.cwd } dir, err := os.Getwd() @@ -735,6 +740,9 @@ func (env *ShellEnvironment) TemplateCache() *TemplateCache { pwd := env.Pwd() tmplCache.PWD = ReplaceHomeDirPrefixWithTilde(env, pwd) tmplCache.Folder = Base(env, pwd) + if env.GOOS() == WINDOWS && strings.HasSuffix(tmplCache.Folder, ":") { + tmplCache.Folder += `\` + } tmplCache.UserName = env.User() if host, err := env.Host(); err == nil { tmplCache.HostName = host @@ -766,19 +774,21 @@ func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (matc } func dirMatchesOneOf(dir, home, goos string, regexes []string) bool { - normalizedCwd := strings.ReplaceAll(dir, "\\", "/") - normalizedHomeDir := strings.ReplaceAll(home, "\\", "/") + if goos == WINDOWS { + dir = strings.ReplaceAll(dir, "\\", "/") + home = strings.ReplaceAll(home, "\\", "/") + } for _, element := range regexes { normalizedElement := strings.ReplaceAll(element, "\\\\", "/") if strings.HasPrefix(normalizedElement, "~") { - normalizedElement = strings.Replace(normalizedElement, "~", normalizedHomeDir, 1) + normalizedElement = strings.Replace(normalizedElement, "~", home, 1) } pattern := fmt.Sprintf("^%s$", normalizedElement) if goos == WINDOWS || goos == DARWIN { pattern = "(?i)" + pattern } - matched := regex.MatchString(pattern, normalizedCwd) + matched := regex.MatchString(pattern, dir) if matched { return true } @@ -786,7 +796,7 @@ func dirMatchesOneOf(dir, home, goos string, regexes []string) bool { return false } -func isPathSeparator(env Environment, c uint8) bool { +func IsPathSeparator(env Environment, c uint8) bool { if c == '/' { return true } @@ -800,14 +810,14 @@ func isPathSeparator(env Environment, c uint8) bool { // 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 { - if path == "/" { - return path - } volumeName := filepath.VolumeName(path) // Strip trailing slashes. - for len(path) > 0 && isPathSeparator(env, path[len(path)-1]) { + 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 } @@ -815,22 +825,69 @@ func Base(env Environment, path string) string { path = path[len(filepath.VolumeName(path)):] // Find the last element i := len(path) - 1 - for i >= 0 && !isPathSeparator(env, path[i]) { + for i >= 0 && !IsPathSeparator(env, path[i]) { i-- } if i >= 0 { path = path[i+1:] } // If empty now, it had only slashes. - if path == "" { + if len(path) == 0 { return env.PathSeparator() } return path } +// ParsePath parses an input path and returns a clean root and a clean path. +func ParsePath(env Environment, inputPath string) (root, path string) { + if len(inputPath) == 0 { + return + } + separator := env.PathSeparator() + clean := func(path string) string { + matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P[^\%s]+)`, separator), path) + n := len(matches) - 1 + s := new(strings.Builder) + for i, m := range matches { + s.WriteString(m["element"]) + if i != n { + s.WriteString(separator) + } + } + return s.String() + } + + if env.GOOS() == WINDOWS { + inputPath = strings.ReplaceAll(inputPath, "/", `\`) + // for a UNC path, extract \\hostname\sharename as the root + matches := regex.FindNamedRegexMatch(`^\\\\(?P[^\\]+)\\+(?P[^\\]+)\\*(?P[\s\S]*)$`, inputPath) + if len(matches) > 0 { + root = `\\` + matches["hostname"] + `\` + matches["sharename"] + `\` + path = clean(matches["path"]) + return + } + } + s := strings.SplitAfterN(inputPath, separator, 2) + root = s[0] + if !strings.HasSuffix(root, separator) { + // a root should end with a separator + root += separator + } + if len(s) == 2 { + path = clean(s[1]) + } + return root, path +} + func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string { - if strings.HasPrefix(path, env.Home()) { - return strings.Replace(path, env.Home(), "~", 1) + home := env.Home() + // match Home directory exactly + if !strings.HasPrefix(path, home) { + return path + } + rem := path[len(home):] + if len(rem) == 0 || IsPathSeparator(env, rem[0]) { + return "~" + rem } return path } diff --git a/src/environment/shell_test.go b/src/environment/shell_test.go index 5653ece8..36432296 100644 --- a/src/environment/shell_test.go +++ b/src/environment/shell_test.go @@ -22,29 +22,6 @@ func TestHostNameWithLan(t *testing.T) { assert.Equal(t, "hello", cleanHostName) } -func TestWindowsPathWithDriveLetter(t *testing.T) { - cases := []struct { - Case string - CWD string - Expected string - }{ - {Case: "C drive", CWD: `C:\Windows\`, Expected: `C:\Windows\`}, - {Case: "C drive lower case", CWD: `c:\Windows\`, Expected: `C:\Windows\`}, - {Case: "P drive lower case", CWD: `p:\some\`, Expected: `P:\some\`}, - {Case: "some drive lower case", CWD: `some:\some\`, Expected: `some:\some\`}, - {Case: "drive ending in c:", CWD: `src:\source\`, Expected: `src:\source\`}, - {Case: "registry drive", CWD: `HKLM:\SOFTWARE\magnetic:test\`, Expected: `HKLM:\SOFTWARE\magnetic:test\`}, - } - for _, tc := range cases { - env := &ShellEnvironment{ - CmdFlags: &Flags{ - PWD: tc.CWD, - }, - } - assert.Equal(t, env.Pwd(), tc.Expected) - } -} - func TestDirMatchesOneOf(t *testing.T) { cases := []struct { GOOS string diff --git a/src/segments/path.go b/src/segments/path.go index fbaa33cd..120e1fc6 100644 --- a/src/segments/path.go +++ b/src/segments/path.go @@ -5,8 +5,8 @@ import ( "oh-my-posh/environment" "oh-my-posh/properties" "oh-my-posh/regex" + "oh-my-posh/shell" "oh-my-posh/template" - "path/filepath" "sort" "strings" ) @@ -45,7 +45,7 @@ const ( Full string = "full" // Folder displays the current folder Folder string = "folder" - // Mixed like agnoster, but if the path is short it displays it + // Mixed like agnoster, but if a folder name is short enough, it is displayed as-is Mixed string = "mixed" // Letter like agnoster, but with the first letter of each folder name Letter string = "letter" @@ -70,64 +70,33 @@ func (pt *Path) Template() string { } func (pt *Path) Enabled() bool { - pt.pwd = pt.env.Pwd() - switch style := pt.props.GetString(properties.Style, Agnoster); style { - case Agnoster: - pt.Path = pt.getAgnosterPath() - case AgnosterFull: - pt.Path = pt.getAgnosterFullPath() - case AgnosterShort: - pt.Path = pt.getAgnosterShortPath() - case Mixed: - pt.Path = pt.getMixedPath() - case Letter: - pt.Path = pt.getLetterPath() - case Unique: - pt.Path = pt.getUniqueLettersPath() - case AgnosterLeft: - pt.Path = pt.getAgnosterLeftPath() - case Short: - // "short" is a duplicate of "full", just here for backwards compatibility - fallthrough - case Full: - pt.Path = pt.getFullPath() - case Folder: - pt.Path = pt.getFolderPath() - default: - pt.Path = fmt.Sprintf("Path style: %s is not available", style) - } - pt.Path = pt.formatWindowsDrive(pt.Path) + pt.setPath() if pt.env.IsWsl() { pt.Location, _ = pt.env.RunCommand("wslpath", "-m", pt.pwd) } else { pt.Location = pt.pwd } - pt.StackCount = pt.env.StackCount() pt.Writable = pt.env.DirIsWritable(pt.pwd) return true } func (pt *Path) Parent() string { - if pt.pwd == pt.env.Home() { + pwd := pt.getPwd() + if len(pwd) == 0 { return "" } - parent := filepath.Dir(pt.pwd) - if pt.pwd == parent { + root, path := environment.ParsePath(pt.env, pwd) + if len(path) == 0 { + // a root path has no parent return "" } - separator := pt.env.PathSeparator() - if parent == pt.rootLocation() || parent == separator { - separator = "" + base := environment.Base(pt.env, path) + path = pt.replaceFolderSeparators(path[:len(path)-len(base)]) + if root != pt.env.PathSeparator() { + root = root[:len(root)-1] + pt.getFolderSeparator() } - return pt.replaceMappedLocations(parent) + separator -} - -func (pt *Path) formatWindowsDrive(pwd string) string { - if pt.env.GOOS() != environment.WINDOWS || !strings.HasSuffix(pwd, ":") { - return pwd - } - return pwd + "\\" + return root + path } func (pt *Path) Init(props properties.Properties, env environment.Environment) { @@ -135,10 +104,52 @@ func (pt *Path) Init(props properties.Properties, env environment.Environment) { pt.env = env } +func (pt *Path) setPath() { + pwd := pt.getPwd() + if len(pwd) == 0 { + return + } + root, path := environment.ParsePath(pt.env, pwd) + if len(path) == 0 { + pt.Path = pt.formatRoot(root) + return + } + switch style := pt.props.GetString(properties.Style, Agnoster); style { + case Agnoster: + pt.Path = pt.getAgnosterPath(root, path) + case AgnosterFull: + pt.Path = pt.getAgnosterFullPath(root, path) + case AgnosterShort: + pt.Path = pt.getAgnosterShortPath(root, path) + case Mixed: + pt.Path = pt.getMixedPath(root, path) + case Letter: + pt.Path = pt.getLetterPath(root, path) + case Unique: + pt.Path = pt.getUniqueLettersPath(root, path) + case AgnosterLeft: + pt.Path = pt.getAgnosterLeftPath(root, path) + case Short: + // "short" is a duplicate of "full", just here for backwards compatibility + fallthrough + case Full: + pt.Path = pt.getFullPath(root, path) + case Folder: + pt.Path = pt.getFolderPath(path) + default: + pt.Path = fmt.Sprintf("Path style: %s is not available", style) + } +} + func (pt *Path) getFolderSeparator() string { separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "") if len(separatorTemplate) == 0 { - return pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator()) + separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator()) + // if empty, use the default separator + if len(separator) == 0 { + return pt.env.PathSeparator() + } + return separator } tmpl := &template.Text{ Template: separatorTemplate, @@ -149,67 +160,66 @@ func (pt *Path) getFolderSeparator() string { if err != nil { pt.env.Log(environment.Error, "getFolderSeparator", err.Error()) } + if len(text) == 0 { + return pt.env.PathSeparator() + } return text } -func (pt *Path) getMixedPath() string { +func (pt *Path) getMixedPath(root, path string) string { var buffer strings.Builder - pwd := pt.getPwd() - splitted := strings.Split(pwd, pt.env.PathSeparator()) threshold := int(pt.props.GetFloat64(MixedThreshold, 4)) - for i, part := range splitted { - if part == "" { - continue - } - - folder := part - if len(part) > threshold && i != 0 && i != len(splitted)-1 { - folder = pt.props.GetString(FolderIcon, "..") - } - separator := pt.getFolderSeparator() - if i == 0 { - separator = "" + folderIcon := pt.props.GetString(FolderIcon, "..") + separator := pt.getFolderSeparator() + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + n := len(elements) + buffer.WriteString(elements[0]) + for i := 1; i < n; i++ { + folder := elements[i] + if len(folder) > threshold && i != n-1 { + folder = folderIcon } buffer.WriteString(fmt.Sprintf("%s%s", separator, folder)) } - return buffer.String() } -func (pt *Path) getAgnosterPath() string { +func (pt *Path) getAgnosterPath(root, path string) string { var buffer strings.Builder - pwd := pt.getPwd() - buffer.WriteString(pt.rootLocation()) - pathDepth := pt.pathDepth(pwd) folderIcon := pt.props.GetString(FolderIcon, "..") separator := pt.getFolderSeparator() - for i := 1; i < pathDepth; i++ { + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + n := len(elements) + buffer.WriteString(elements[0]) + for i := 2; i < n; i++ { buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon)) } - if pathDepth > 0 { - buffer.WriteString(fmt.Sprintf("%s%s", separator, environment.Base(pt.env, pwd))) + if n > 1 { + buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1])) } return buffer.String() } -func (pt *Path) getAgnosterLeftPath() string { - pwd := pt.getPwd() - separator := pt.env.PathSeparator() - pwd = strings.Trim(pwd, separator) - splitted := strings.Split(pwd, separator) - folderIcon := pt.props.GetString(FolderIcon, "..") - separator = pt.getFolderSeparator() - switch len(splitted) { - case 0: - return "" - case 1: - return splitted[0] - case 2: - return fmt.Sprintf("%s%s%s", splitted[0], separator, splitted[1]) - } +func (pt *Path) getAgnosterLeftPath(root, path string) string { var buffer strings.Builder - buffer.WriteString(fmt.Sprintf("%s%s%s", splitted[0], separator, splitted[1])) - for i := 2; i < len(splitted); i++ { + folderIcon := pt.props.GetString(FolderIcon, "..") + separator := pt.getFolderSeparator() + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + n := len(elements) + buffer.WriteString(elements[0]) + if n > 1 { + buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[1])) + } + for i := 2; i < n; i++ { buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon)) } return buffer.String() @@ -218,7 +228,7 @@ func (pt *Path) getAgnosterLeftPath() string { func (pt *Path) getRelevantLetter(folder string) string { // check if there is at least a letter we can use matches := regex.FindNamedRegexMatch(`(?P[\p{L}0-9]).*`, folder) - if matches == nil || matches["letter"] == "" { + if matches == nil || len(matches["letter"]) == 0 { // no letter found, keep the folder unchanged return folder } @@ -228,36 +238,36 @@ func (pt *Path) getRelevantLetter(folder string) string { return letter } -func (pt *Path) getLetterPath() string { +func (pt *Path) getLetterPath(root, path string) string { var buffer strings.Builder - pwd := pt.getPwd() - splitted := strings.Split(pwd, pt.env.PathSeparator()) separator := pt.getFolderSeparator() - for i := 0; i < len(splitted)-1; i++ { - folder := splitted[i] - if len(folder) == 0 { - continue + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + n := len(elements) + for i := 0; i < n-1; i++ { + letter := pt.getRelevantLetter(elements[i]) + if i != 0 { + buffer.WriteString(separator) } - letter := pt.getRelevantLetter(folder) - buffer.WriteString(fmt.Sprintf("%s%s", letter, separator)) - } - if len(splitted) > 0 { - buffer.WriteString(splitted[len(splitted)-1]) + buffer.WriteString(letter) } + buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1])) return buffer.String() } -func (pt *Path) getUniqueLettersPath() string { +func (pt *Path) getUniqueLettersPath(root, path string) string { var buffer strings.Builder - pwd := pt.getPwd() - splitted := strings.Split(pwd, pt.env.PathSeparator()) separator := pt.getFolderSeparator() - letters := make(map[string]bool, len(splitted)) - for i := 0; i < len(splitted)-1; i++ { - folder := splitted[i] - if len(folder) == 0 { - continue - } + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + n := len(elements) + letters := make(map[string]bool) + for i := 0; i < n-1; i++ { + folder := elements[i] letter := pt.getRelevantLetter(folder) for letters[letter] { if letter == folder { @@ -266,102 +276,126 @@ func (pt *Path) getUniqueLettersPath() string { letter += folder[len(letter) : len(letter)+1] } letters[letter] = true - buffer.WriteString(fmt.Sprintf("%s%s", letter, separator)) - } - if len(splitted) > 0 { - buffer.WriteString(splitted[len(splitted)-1]) + if i != 0 { + buffer.WriteString(separator) + } + buffer.WriteString(letter) } + buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1])) return buffer.String() } -func (pt *Path) getAgnosterFullPath() string { - pwd := pt.getPwd() - for len(pwd) > 1 && string(pwd[0]) == pt.env.PathSeparator() { - pwd = pwd[1:] +func (pt *Path) getAgnosterFullPath(root, path string) string { + path = pt.replaceFolderSeparators(path) + if root == pt.env.PathSeparator() { + return path } - return pt.replaceFolderSeparators(pwd) + root = root[:len(root)-1] + pt.getFolderSeparator() + return root + path } -func (pt *Path) getAgnosterShortPath() string { - pwd := pt.getPwd() - pathDepth := pt.pathDepth(pwd) +func (pt *Path) getAgnosterShortPath(root, path string) string { + elements := strings.Split(path, pt.env.PathSeparator()) + if root != pt.env.PathSeparator() { + elements = append([]string{root[:len(root)-1]}, elements...) + } + depth := len(elements) maxDepth := pt.props.GetInt(MaxDepth, 1) if maxDepth < 1 { maxDepth = 1 } hideRootLocation := pt.props.GetBool(HideRootLocation, false) - if hideRootLocation { - // 1-indexing to avoid showing the root location when exceeding the max depth - pathDepth++ + if !hideRootLocation { + maxDepth++ } - if pathDepth <= maxDepth { - return pt.getAgnosterFullPath() + if depth <= maxDepth { + return pt.getAgnosterFullPath(root, path) } - folderSeparator := pt.getFolderSeparator() - pathSeparator := pt.env.PathSeparator() - splitted := strings.Split(pwd, pathSeparator) - fullPathDepth := len(splitted) - splitPos := fullPathDepth - maxDepth + separator := pt.getFolderSeparator() + folderIcon := pt.props.GetString(FolderIcon, "..") var buffer strings.Builder - if hideRootLocation { - buffer.WriteString(splitted[splitPos]) - splitPos++ - } else { - folderIcon := pt.props.GetString(FolderIcon, "..") - root := pt.rootLocation() - buffer.WriteString(fmt.Sprintf("%s%s%s", root, folderSeparator, folderIcon)) + if !hideRootLocation { + buffer.WriteString(fmt.Sprintf("%s%s", elements[0], separator)) + maxDepth-- } - for i := splitPos; i < fullPathDepth; i++ { - buffer.WriteString(fmt.Sprintf("%s%s", folderSeparator, splitted[i])) + splitPos := depth - maxDepth + if splitPos != 1 { + buffer.WriteString(fmt.Sprintf("%s%s", folderIcon, separator)) + } + for i := splitPos; i < depth; i++ { + buffer.WriteString(elements[i]) + if i != depth-1 { + buffer.WriteString(separator) + } } return buffer.String() } -func (pt *Path) getFullPath() string { - pwd := pt.getPwd() - return pt.replaceFolderSeparators(pwd) +func (pt *Path) getFullPath(root, path string) string { + if root != pt.env.PathSeparator() { + root = root[:len(root)-1] + pt.getFolderSeparator() + } + path = pt.replaceFolderSeparators(path) + return root + path } -func (pt *Path) getFolderPath() string { - pwd := pt.getPwd() - pwd = environment.Base(pt.env, pwd) - return pt.replaceFolderSeparators(pwd) +func (pt *Path) getFolderPath(path string) string { + return environment.Base(pt.env, path) +} + +func (pt *Path) setPwd() { + if len(pt.pwd) > 0 { + return + } + if pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5 { + pt.pwd = pt.env.Flags().PSWD + } + if len(pt.pwd) == 0 { + pt.pwd = pt.env.Pwd() + return + } + // ensure a clean path + root, path := environment.ParsePath(pt.env, pt.pwd) + pt.pwd = root + path } func (pt *Path) getPwd() string { - pwd := pt.env.Flags().PSWD - if pwd == "" { - pwd = pt.env.Pwd() + pt.setPwd() + return pt.replaceMappedLocations(pt.pwd) +} + +func (pt *Path) formatRoot(root string) string { + n := len(root) + // trim the trailing separator first + root = root[:n-1] + // only preserve the trailing separator for a Unix/Windows/PSDrive root + if len(root) == 0 || (strings.HasPrefix(pt.pwd, root) && strings.HasSuffix(root, ":")) { + return root + pt.env.PathSeparator() } - pwd = pt.replaceMappedLocations(pwd) - return pwd + return root } func (pt *Path) normalize(inputPath string) string { normalized := inputPath - if strings.HasPrefix(inputPath, "~") { + if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || environment.IsPathSeparator(pt.env, normalized[1])) { normalized = pt.env.Home() + normalized[1:] } - normalized = strings.ReplaceAll(normalized, "\\", "/") - goos := pt.env.GOOS() - if goos == environment.WINDOWS || goos == environment.DARWIN { + switch pt.env.GOOS() { + case environment.WINDOWS: + normalized = strings.ReplaceAll(normalized, "/", `\`) + fallthrough + case environment.DARWIN: normalized = strings.ToLower(normalized) } - if !strings.HasSuffix(normalized, "/") { - normalized += "/" - } return normalized } func (pt *Path) replaceMappedLocations(pwd string) string { - if strings.HasPrefix(pwd, "Microsoft.PowerShell.Core\\FileSystem::") { - pwd = strings.Replace(pwd, "Microsoft.PowerShell.Core\\FileSystem::", "", 1) - } - mappedLocations := map[string]string{} + // predefined mapped locations, can be disabled if pt.props.GetBool(MappedLocationsEnabled, true) { - mappedLocations[pt.normalize("hkcu:")] = pt.props.GetString(WindowsRegistryIcon, "\uF013") - mappedLocations[pt.normalize("hklm:")] = pt.props.GetString(WindowsRegistryIcon, "\uF013") + mappedLocations["hkcu:"] = pt.props.GetString(WindowsRegistryIcon, "\uF013") + mappedLocations["hklm:"] = pt.props.GetString(WindowsRegistryIcon, "\uF013") mappedLocations[pt.normalize(pt.env.Home())] = pt.props.GetString(HomeIcon, "~") } @@ -369,37 +403,48 @@ func (pt *Path) replaceMappedLocations(pwd string) string { // mapped locations can override predefined locations keyValues := pt.props.GetKeyValueMap(MappedLocations, make(map[string]string)) for key, val := range keyValues { - mappedLocations[pt.normalize(key)] = val + if key != "" { + mappedLocations[pt.normalize(key)] = val + } } // sort map keys in reverse order // fixes case when a subfoder and its parent are mapped // ex /users/test and /users/test/dev - keys := make([]string, len(mappedLocations)) - i := 0 + keys := make([]string, 0, len(mappedLocations)) for k := range mappedLocations { - keys[i] = k - i++ + keys = append(keys, k) } sort.Sort(sort.Reverse(sort.StringSlice(keys))) - normalizedPwd := pt.normalize(pwd) + cleanPwdRoot, cleanPwdPath := environment.ParsePath(pt.env, pwd) + pwdRoot := pt.normalize(cleanPwdRoot) + pwdPath := pt.normalize(cleanPwdPath) for _, key := range keys { - if strings.HasPrefix(normalizedPwd, key) { - replacement := mappedLocations[key] - // -1 as we want to ignore the trailing slash - // set by the normalize function - return replacement + pwd[len(key)-1:] + keyRoot, keyPath := environment.ParsePath(pt.env, key) + if keyRoot != pwdRoot || !strings.HasPrefix(pwdPath, keyPath) { + continue + } + value := mappedLocations[key] + rem := cleanPwdPath[len(keyPath):] + if len(rem) == 0 { + // exactly match the full path + return value + } + if len(keyPath) == 0 { + // only match the root + return value + pt.env.PathSeparator() + cleanPwdPath + } + // match several prefix elements + if rem[0:1] == pt.env.PathSeparator() { + return value + rem } } - return pwd + return cleanPwdRoot + cleanPwdPath } func (pt *Path) replaceFolderSeparators(pwd string) string { defaultSeparator := pt.env.PathSeparator() - if pwd == defaultSeparator { - return pwd - } folderSeparator := pt.getFolderSeparator() if folderSeparator == defaultSeparator { return pwd @@ -408,22 +453,3 @@ func (pt *Path) replaceFolderSeparators(pwd string) string { pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator) return pwd } - -func (pt *Path) rootLocation() string { - pwd := pt.getPwd() - pwd = strings.TrimPrefix(pwd, pt.env.PathSeparator()) - splitted := strings.Split(pwd, pt.env.PathSeparator()) - rootLocation := splitted[0] - return rootLocation -} - -func (pt *Path) pathDepth(pwd string) int { - splitted := strings.Split(pwd, pt.env.PathSeparator()) - depth := 0 - for _, part := range splitted { - if part != "" { - depth++ - } - } - return depth - 1 -} diff --git a/src/segments/path_test.go b/src/segments/path_test.go index 78b0921d..89a060eb 100644 --- a/src/segments/path_test.go +++ b/src/segments/path_test.go @@ -4,8 +4,8 @@ import ( "oh-my-posh/environment" "oh-my-posh/mock" "oh-my-posh/properties" + "oh-my-posh/shell" "oh-my-posh/template" - "runtime" "strings" "testing" @@ -40,81 +40,107 @@ func renderTemplate(env *mock.MockedEnvironment, segmentTemplate string, context } const ( - homeJan = "/usr/home/jan" - homeBillWindows = "C:\\Users\\Bill" - levelDir = "/level" + homeDir = "/home/someone" + homeDirWindows = "C:\\Users\\someone" ) -func TestRootLocationHome(t *testing.T) { - cases := []struct { - Expected string - HomePath string - Pswd string - Pwd string - GOOS string - PathSeparator string - HomeIcon string - RegistryIcon string - }{ - {Expected: "REG", RegistryIcon: "REG", HomePath: "C:\\Users\\Bill", Pwd: "HKCU:\\Program Files\\Go", GOOS: environment.WINDOWS, PathSeparator: "\\"}, - {Expected: "~", HomeIcon: "~", HomePath: "/home/bill/", Pwd: "/home/bill/", PathSeparator: "/"}, - {Expected: "usr", HomePath: "/home/bill/", Pwd: "/usr/error/what", PathSeparator: "/"}, - {Expected: "C:", HomePath: "C:\\Users\\Bill", Pwd: "C:\\Program Files\\Go", GOOS: environment.WINDOWS, PathSeparator: "\\"}, - {Expected: "~", HomeIcon: "~", HomePath: "C:\\Users\\Bill", Pwd: "Microsoft.PowerShell.Core\\FileSystem::C:\\Users\\Bill", GOOS: environment.WINDOWS, PathSeparator: "\\"}, - {Expected: "C:", HomePath: "C:\\Users\\Jack", Pwd: "Microsoft.PowerShell.Core\\FileSystem::C:\\Users\\Bill", GOOS: environment.WINDOWS, PathSeparator: "\\"}, - {Expected: "", HomePath: "C:\\Users\\Jack", Pwd: "", GOOS: environment.WINDOWS, PathSeparator: "\\"}, - {Expected: "DRIVE:", HomePath: "/home/bill/", Pwd: "/usr/error/what", Pswd: "DRIVE:", PathSeparator: "/"}, - } - for _, tc := range cases { - env := new(mock.MockedEnvironment) - env.On("Home").Return(tc.HomePath) - env.On("Pwd").Return(tc.Pwd) - args := &environment.Flags{ - PSWD: tc.Pswd, - } - env.On("Flags").Return(args) - env.On("PathSeparator").Return(tc.PathSeparator) - env.On("GOOS").Return(tc.GOOS) - path := &Path{ - env: env, - props: properties.Map{ - HomeIcon: tc.HomeIcon, - WindowsRegistryIcon: tc.RegistryIcon, - }, - } - got := path.rootLocation() - assert.EqualValues(t, tc.Expected, got) - } -} - func TestParent(t *testing.T) { - // there's no Windows support/validation for this just yet - // mainly due to root being a special case - if runtime.GOOS == environment.WINDOWS { - return - } cases := []struct { - Case string - Expected string - HomePath string - Pwd string - PathSeparator string + Case string + Expected string + HomePath string + Pwd string + GOOS string + PathSeparator string + FolderSeparatorIcon string }{ - {Case: "Home folder", Expected: "", HomePath: "/home/bill", Pwd: "/home/bill", PathSeparator: "/"}, - {Case: "Inside home folder", Expected: "~/", HomePath: "/home/bill", Pwd: "/home/bill/test", PathSeparator: "/"}, - {Case: "Root", Expected: "", HomePath: "/home/bill", Pwd: "/", PathSeparator: "/"}, - {Case: "Root + 1", Expected: "/", HomePath: "/home/bill", Pwd: "/usr", PathSeparator: "/"}, + { + Case: "Home folder", + HomePath: homeDir, + Pwd: homeDir, + GOOS: environment.DARWIN, + PathSeparator: "/", + }, + { + Case: "Home folder with a trailing separator", + HomePath: homeDir, + Pwd: homeDir + "/", + GOOS: environment.DARWIN, + PathSeparator: "/", + }, + { + Case: "Inside Home folder", + Expected: "~/", + HomePath: homeDir, + Pwd: homeDir + "/test", + GOOS: environment.DARWIN, + PathSeparator: "/", + }, + { + Case: "Root", + HomePath: homeDir, + Pwd: "/", + GOOS: environment.DARWIN, + PathSeparator: "/", + }, + { + Case: "Root + 1", + Expected: "/", + HomePath: homeDir, + Pwd: "/usr", + GOOS: environment.DARWIN, + PathSeparator: "/", + }, + { + Case: "Windows Home folder", + HomePath: homeDirWindows, + Pwd: homeDirWindows, + GOOS: environment.WINDOWS, + PathSeparator: "\\", + }, + { + Case: "Windows drive root", + HomePath: homeDirWindows, + Pwd: "C:", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + }, + { + Case: "Windows drive root with a trailing separator", + HomePath: homeDirWindows, + Pwd: "C:\\", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + }, + { + Case: "Windows drive root + 1", + Expected: "C:\\", + HomePath: homeDirWindows, + Pwd: "C:\\test", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + }, + { + Case: "PSDrive root", + HomePath: homeDirWindows, + Pwd: "HKLM:", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + }, } for _, tc := range cases { env := new(mock.MockedEnvironment) env.On("Home").Return(tc.HomePath) env.On("Pwd").Return(tc.Pwd) env.On("Flags").Return(&environment.Flags{}) + env.On("Shell").Return(shell.PLAIN) env.On("PathSeparator").Return(tc.PathSeparator) - env.On("GOOS").Return(environment.DARWIN) + env.On("GOOS").Return(tc.GOOS) path := &Path{ - env: env, - props: properties.Map{}, + env: env, + props: properties.Map{ + FolderSeparatorIcon: tc.FolderSeparatorIcon, + }, } path.pwd = tc.Pwd got := path.Parent() @@ -122,45 +148,9 @@ func TestParent(t *testing.T) { } } -func TestPathDepthMultipleLevelsDeep(t *testing.T) { - pwd := "/usr" - for i := 0; i < 99; i++ { - pwd += levelDir - } - env := new(mock.MockedEnvironment) - env.On("PathSeparator").Return("/") - env.On("getRunteGOOS").Return("") - path := &Path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 99, got) -} - -func TestPathDepthZeroLevelsDeep(t *testing.T) { - pwd := "/usr/" - env := new(mock.MockedEnvironment) - env.On("PathSeparator").Return("/") - path := &Path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 0, got) -} - -func TestPathDepthOneLevelDeep(t *testing.T) { - pwd := "/usr/location" - env := new(mock.MockedEnvironment) - env.On("PathSeparator").Return("/") - path := &Path{ - env: env, - } - got := path.pathDepth(pwd) - assert.Equal(t, 1, got) -} - func TestAgnosterPathStyles(t *testing.T) { cases := []struct { + Style string Expected string HomePath string Pswd string @@ -168,29 +158,225 @@ func TestAgnosterPathStyles(t *testing.T) { PathSeparator string HomeIcon string FolderSeparatorIcon string - Style string GOOS string MaxDepth int HideRootLocation bool }{ - {Style: AgnosterFull, Expected: "usr > location > whatever", HomePath: "/usr/home", Pwd: "/usr/location/whatever", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "usr > .. > man", HomePath: "/usr/home", Pwd: "/usr/location/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "~ > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "~ > projects", HomePath: "/usr/home", Pwd: "/usr/home/projects", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "C:", HomePath: homeBillWindows, Pwd: "C:", PathSeparator: "\\", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "/", HomePath: homeBillWindows, Pwd: "/", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "foo", HomePath: homeBillWindows, Pwd: "/foo", PathSeparator: "/", FolderSeparatorIcon: " > "}, - - {Style: AgnosterShort, Expected: "usr > .. > bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2}, - {Style: AgnosterShort, Expected: "usr > foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 3}, - {Style: AgnosterShort, Expected: "~ > .. > bar > man", HomePath: "/usr/home", Pwd: "/usr/home/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2}, - {Style: AgnosterShort, Expected: "~ > foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/home/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 3}, + { + 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: "usr > .. > man", + HomePath: homeDir, + Pwd: "/usr/location/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "~ > .. > man", + HomePath: homeDir, + Pwd: homeDir + "/whatever/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "~ > projects", + HomePath: homeDir, + Pwd: homeDir + "/projects", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "usr > .. > bar > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + }, + { + Style: AgnosterShort, + Expected: "usr > foo > bar > man", + HomePath: homeDir, + Pwd: "/usr/foo/bar/man", + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 3, + }, + { + 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: "src | init", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src/init", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "PSDRIVE: | src", + HomePath: homeDir, + Pwd: "/foo", + Pswd: "PSDRIVE:/src", + PathSeparator: "/", + FolderSeparatorIcon: " | ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~", + HomePath: homeDir, + Pwd: homeDir, + PathSeparator: "/", + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + 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, + }, + { + Style: AgnosterShort, + Expected: "C:\\", + HomePath: homeDirWindows, + Pwd: "C:", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, { Style: AgnosterShort, Expected: "C: > .. > bar > man", - HomePath: homeBillWindows, + HomePath: homeDirWindows, Pwd: "C:\\usr\\foo\\bar\\man", + GOOS: environment.WINDOWS, PathSeparator: "\\", FolderSeparatorIcon: " > ", MaxDepth: 2, @@ -198,8 +384,9 @@ func TestAgnosterPathStyles(t *testing.T) { { Style: AgnosterShort, Expected: "C: > .. > foo > bar > man", - HomePath: homeBillWindows, + HomePath: homeDirWindows, Pwd: "C:\\usr\\foo\\bar\\man", + GOOS: environment.WINDOWS, PathSeparator: "\\", FolderSeparatorIcon: " > ", MaxDepth: 3, @@ -207,8 +394,9 @@ func TestAgnosterPathStyles(t *testing.T) { { Style: AgnosterShort, Expected: "~ > .. > bar > man", - HomePath: homeBillWindows, - Pwd: "C:\\Users\\Bill\\foo\\bar\\man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo\\bar\\man", + GOOS: environment.WINDOWS, PathSeparator: "\\", FolderSeparatorIcon: " > ", MaxDepth: 2, @@ -216,61 +404,275 @@ func TestAgnosterPathStyles(t *testing.T) { { Style: AgnosterShort, Expected: "~ > foo > bar > man", - HomePath: homeBillWindows, - Pwd: "C:\\Users\\Bill\\foo\\bar\\man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo\\bar\\man", + GOOS: environment.WINDOWS, PathSeparator: "\\", FolderSeparatorIcon: " > ", MaxDepth: 3, }, + { + Style: AgnosterShort, + Expected: "~", + HomePath: homeDirWindows, + Pwd: homeDirWindows, + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "foo", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + MaxDepth: 1, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "~ > foo", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: ".. > bar > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\foo\\bar\\man", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + MaxDepth: 2, + HideRootLocation: true, + }, + { + Style: AgnosterShort, + Expected: "\\\\localhost\\c$", + HomePath: homeDirWindows, + Pwd: "\\\\localhost\\c$", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, + { + Style: AgnosterShort, + Expected: "\\\\localhost\\c$ > some", + HomePath: homeDirWindows, + Pwd: "\\\\localhost\\c$\\some", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, - {Style: AgnosterFull, Expected: "PSDRIVE: | src", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src", PathSeparator: "/", FolderSeparatorIcon: " | "}, - {Style: AgnosterShort, Expected: "PSDRIVE: | .. | init", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src/init", 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: Mixed, + Expected: "C: > .. > foo > .. > man", + HomePath: homeDirWindows, + Pwd: "C:\\Users\\foo\\foobar\\man", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, - {Style: AgnosterShort, Expected: "src | init", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src/init", PathSeparator: "/", FolderSeparatorIcon: " | ", MaxDepth: 2, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "PSDRIVE: | src", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src", PathSeparator: "/", FolderSeparatorIcon: " | ", MaxDepth: 2, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~", HomePath: homeBillWindows, Pwd: homeBillWindows, PathSeparator: "\\", FolderSeparatorIcon: " > ", MaxDepth: 1, HideRootLocation: true}, - {Style: AgnosterShort, Expected: "foo", HomePath: homeBillWindows, Pwd: homeBillWindows + "\\foo", PathSeparator: "\\", FolderSeparatorIcon: "\\", MaxDepth: 1, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~\\foo", HomePath: homeBillWindows, Pwd: homeBillWindows + "\\foo", PathSeparator: "\\", FolderSeparatorIcon: "\\", MaxDepth: 2, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~", HomePath: "/usr/home", Pwd: "/usr/home", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 1, HideRootLocation: true}, - {Style: AgnosterShort, Expected: "foo", HomePath: "/usr/home", Pwd: "/usr/home/foo", PathSeparator: "/", FolderSeparatorIcon: "/", MaxDepth: 1, HideRootLocation: true}, - {Style: AgnosterShort, Expected: "bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 3, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~ > foo", HomePath: "/usr/home", Pwd: "/usr/home/foo", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2, HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~ > foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/home/foo/bar/man", PathSeparator: "/", FolderSeparatorIcon: " > ", MaxDepth: 4, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "C:", HomePath: "/usr/home", Pwd: "/mnt/c", Pswd: "C:", PathSeparator: "/", FolderSeparatorIcon: " | ", MaxDepth: 2, HideRootLocation: true}, - {Style: AgnosterShort, Expected: "~ | space foo", HomePath: "/usr/home", Pwd: "/usr/home/space foo", PathSeparator: "/", FolderSeparatorIcon: " | ", MaxDepth: 2, - HideRootLocation: true}, - {Style: AgnosterShort, Expected: "space foo", HomePath: "/usr/home", Pwd: "/usr/home/space foo", PathSeparator: "/", FolderSeparatorIcon: " | ", MaxDepth: 1, - HideRootLocation: true}, + { + 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: "C:\\", + HomePath: homeDirWindows, + Pwd: "C:\\", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "C > s > .w > man", + HomePath: homeDirWindows, + Pwd: "C:\\something\\.whatever\\man", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, + { + Style: Letter, + Expected: "~ > s > man", + HomePath: homeDirWindows, + Pwd: homeDirWindows + "\\something\\man", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, - {Style: Mixed, Expected: "~ > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Mixed, Expected: "~ > ab > .. > man", HomePath: "/usr/home", Pwd: "/usr/home/ab/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - - {Style: Letter, Expected: "~ > a > w > man", HomePath: "/usr/home", Pwd: "/usr/home/ab/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > b > a > w > man", HomePath: "/usr/home", Pwd: "/usr/burp/ab/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > a > w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/ab/whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > a > .w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/ab/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > a > ._w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/ab/._whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .ä > ū > .w > man", HomePath: "/usr/home", Pwd: "/usr/.äufbau/ūmgebung/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > 1 > .w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/12345/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > 1 > .w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/12345abc/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "u > .b > __p > .w > man", HomePath: "/usr/home", Pwd: "/usr/.burp/__pycache__/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "➼ > .w > man", HomePath: "/usr/home", Pwd: "➼/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Letter, Expected: "➼ s > .w > man", HomePath: "/usr/home", Pwd: "➼ something/.whatever/man", PathSeparator: "/", FolderSeparatorIcon: " > "}, - - {Style: Unique, Expected: "~ > a > ab > abcd", HomePath: "/usr/home", Pwd: "/usr/home/ab/abc/abcd", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Unique, Expected: "~ > a > .a > abcd", HomePath: "/usr/home", Pwd: "/usr/home/ab/.abc/abcd", PathSeparator: "/", FolderSeparatorIcon: " > "}, - {Style: Unique, Expected: "~ > a > ab > abcd", HomePath: "/usr/home", Pwd: "/usr/home/ab/ab/abcd", PathSeparator: "/", FolderSeparatorIcon: " > "}, - - {Style: AgnosterShort, Expected: "localhost > c$", HomePath: homeBillWindows, Pwd: "\\\\localhost\\c$", PathSeparator: "\\", FolderSeparatorIcon: " > "}, - {Style: AgnosterShort, Expected: "localhost\\c$", HomePath: homeBillWindows, Pwd: "\\\\localhost\\c$", PathSeparator: "\\", FolderSeparatorIcon: "\\"}, + { + 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: "C > a > ab > abcd", + HomePath: homeDirWindows, + Pwd: "C:\\ab\\ab\\abcd", + GOOS: environment.WINDOWS, + PathSeparator: "\\", + FolderSeparatorIcon: " > ", + }, } for _, tc := range cases { env := new(mock.MockedEnvironment) @@ -280,11 +682,11 @@ func TestAgnosterPathStyles(t *testing.T) { env.On("GOOS").Return(tc.GOOS) env.On("StackCount").Return(0) env.On("IsWsl").Return(false) - env.On("DirIsWritable", tc.Pwd).Return(true) args := &environment.Flags{ PSWD: tc.Pswd, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) path := &Path{ env: env, props: properties.Map{ @@ -294,15 +696,16 @@ func TestAgnosterPathStyles(t *testing.T) { HideRootLocation: tc.HideRootLocation, }, } - _ = path.Enabled() + path.setPath() got := renderTemplate(env, "{{ .Path }}", path) assert.Equal(t, tc.Expected, got) } } -func TestGetFullPath(t *testing.T) { +func TestFullAndFolderPath(t *testing.T) { cases := []struct { Style string + HomePath string FolderSeparatorIcon string Pwd string Pswd string @@ -313,82 +716,74 @@ func TestGetFullPath(t *testing.T) { StackCount int Template string }{ - {Style: Full, Pwd: "/usr/home/abc", Template: "{{ .Path }}", StackCount: 2, Expected: "~/abc"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "", Expected: ""}, {Style: Full, Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "/usr/home", Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "/usr/home/abc", DisableMappedLocations: true}, + {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: "/a/b/c/d", Expected: "/a/b/c/d"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "", Expected: ""}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "~"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "|usr|home", DisableMappedLocations: true}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/usr/home/abc", Expected: "~|abc"}, - {Style: Full, FolderSeparatorIcon: "|", Pwd: "/a/b/c/d", Expected: "|a|b|c|d"}, + {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: "/a/b/c/d", Expected: "/a|b|c|d"}, - {Style: Folder, Pwd: "", Expected: ""}, {Style: Folder, Pwd: "/", Expected: "/"}, - {Style: Folder, Pwd: "/usr/home", Expected: "~"}, - {Style: Folder, Pwd: "/usr/home", Expected: "home", DisableMappedLocations: true}, - {Style: Folder, Pwd: "/usr/home/abc", Expected: "abc"}, + {Style: Folder, Pwd: homeDir, Expected: "~"}, + {Style: Folder, Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, + {Style: Folder, Pwd: homeDir + "/abc", Expected: "abc"}, {Style: Folder, Pwd: "/a/b/c/d", Expected: "d"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "", Expected: ""}, {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "~"}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home", Expected: "home", DisableMappedLocations: true}, - {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/usr/home/abc", Expected: "abc"}, + {Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"}, + {Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, + {Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir + "/abc", Expected: "abc"}, {Style: Folder, FolderSeparatorIcon: "|", Pwd: "/a/b/c/d", Expected: "d"}, + // for Windows paths {Style: Folder, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: environment.WINDOWS}, - {Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\Jan", Expected: "C:\\Users\\Jan", PathSeparator: "\\", GOOS: environment.WINDOWS}, + {Style: Folder, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: environment.WINDOWS}, + {Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: environment.WINDOWS}, + {Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows + "\\abc", Expected: "~\\abc", PathSeparator: "\\", GOOS: environment.WINDOWS}, // StackCountEnabled=true and StackCount=2 {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 2, Expected: "2 /"}, - {Style: Full, Pwd: "", StackCount: 2, Expected: "2"}, {Style: Full, Pwd: "/", StackCount: 2, Expected: "2 /"}, - {Style: Full, Pwd: "/usr/home", StackCount: 2, Expected: "2 ~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: 2, Expected: "2 ~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: 2, Expected: "2 /usr/home/abc", DisableMappedLocations: true}, + {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: "/a/b/c/d", 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: "/", Template: "{{ .Path }}", StackCount: 2, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", Template: "{{ .Path }}", StackCount: 2, Expected: "~"}, + {Style: Full, Pwd: homeDir, Template: "{{ .Path }}", StackCount: 2, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", Template: "{{ .Path }}", StackCount: 2, Expected: "/usr/home/abc", DisableMappedLocations: true}, + {Style: Full, Pwd: homeDir + "/abc", Template: "{{ .Path }}", StackCount: 2, Expected: homeDir + "/abc", DisableMappedLocations: true}, {Style: Full, Pwd: "/a/b/c/d", Template: "{{ .Path }}", StackCount: 2, Expected: "/a/b/c/d"}, // StackCountEnabled=true and StackCount=0 {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: 0, Expected: "/"}, - {Style: Full, Pwd: "", StackCount: 0, Expected: ""}, {Style: Full, Pwd: "/", StackCount: 0, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCount: 0, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: 0, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: 0, Expected: "/usr/home/abc", DisableMappedLocations: true}, + {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: "/a/b/c/d", StackCount: 0, Expected: "/a/b/c/d"}, // StackCountEnabled=true and StackCount<0 {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCount: -1, Expected: "/"}, - {Style: Full, Pwd: "", StackCount: -1, Expected: ""}, {Style: Full, Pwd: "/", StackCount: -1, Expected: "/"}, - {Style: Full, Pwd: "/usr/home", StackCount: -1, Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: -1, Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", StackCount: -1, Expected: "/usr/home/abc", DisableMappedLocations: true}, + {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: "/a/b/c/d", StackCount: -1, Expected: "/a/b/c/d"}, // StackCountEnabled=true and StackCount not set {Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "", Expected: ""}, {Style: Full, Pwd: "/", Expected: "/"}, - {Style: Full, Pwd: "/usr/home", Expected: "~"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "~/abc"}, - {Style: Full, Pwd: "/usr/home/abc", Expected: "/usr/home/abc", DisableMappedLocations: true}, + {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: "/a/b/c/d", Expected: "/a/b/c/d"}, } @@ -398,16 +793,20 @@ func TestGetFullPath(t *testing.T) { tc.PathSeparator = "/" } env.On("PathSeparator").Return(tc.PathSeparator) - env.On("Home").Return("/usr/home") + if tc.GOOS == environment.WINDOWS { + env.On("Home").Return(homeDirWindows) + } else { + env.On("Home").Return(homeDir) + } env.On("Pwd").Return(tc.Pwd) env.On("GOOS").Return(tc.GOOS) env.On("StackCount").Return(tc.StackCount) env.On("IsWsl").Return(false) - env.On("DirIsWritable", tc.Pwd).Return(true) args := &environment.Flags{ PSWD: tc.Pswd, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PLAIN) if len(tc.Template) == 0 { tc.Template = "{{ if gt .StackCount 0 }}{{ .StackCount }} {{ end }}{{ .Path }}" } @@ -421,102 +820,87 @@ func TestGetFullPath(t *testing.T) { props[MappedLocationsEnabled] = false } path := &Path{ - env: env, - props: props, + env: env, + props: props, + StackCount: env.StackCount(), } - _ = path.Enabled() + path.setPath() got := renderTemplate(env, tc.Template, path) assert.Equal(t, tc.Expected, got) } } -func TestGetFullPathCustomMappedLocations(t *testing.T) { +func TestFullPathCustomMappedLocations(t *testing.T) { cases := []struct { Pwd string MappedLocations map[string]string + GOOS string + PathSeparator string Expected string }{ {Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b/c/d": "#"}, Expected: "#"}, - {Pwd: "/a/b/c/d", MappedLocations: map[string]string{"\\a\\b": "#"}, Expected: "#/c/d"}, - {Pwd: "\\a\\b\\c\\d", MappedLocations: map[string]string{"\\a\\b": "#"}, Expected: "#\\c\\d"}, + {Pwd: "\\a\\b\\c\\d", MappedLocations: map[string]string{"\\a\\b": "#"}, GOOS: environment.WINDOWS, PathSeparator: "\\", Expected: "#\\c\\d"}, {Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b": "#"}, Expected: "#/c/d"}, {Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b": "/e/f"}, Expected: "/e/f/c/d"}, - {Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"~\\a\\b": "#"}, Expected: "#/c/d"}, - {Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"~/a/b": "#"}, Expected: "#/c/d"}, - {Pwd: "/a/usr/home/b/c/d", MappedLocations: map[string]string{"/a~": "#"}, Expected: "/a/usr/home/b/c/d"}, - {Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"/a/b": "#"}, Expected: "/usr/home/a/b/c/d"}, + {Pwd: homeDir + "/a/b/c/d", 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 + "/a/b/c/d", MappedLocations: map[string]string{"/a/b": "#"}, Expected: homeDir + "/a/b/c/d"}, } for _, tc := range cases { env := new(mock.MockedEnvironment) - env.On("PathSeparator").Return("/") - env.On("Home").Return("/usr/home") + env.On("Home").Return(homeDir) env.On("Pwd").Return(tc.Pwd) - env.On("GOOS").Return("") + if tc.GOOS == "" { + tc.GOOS = environment.DARWIN + } + env.On("GOOS").Return(tc.GOOS) + if tc.PathSeparator == "" { + tc.PathSeparator = "/" + } + env.On("PathSeparator").Return(tc.PathSeparator) args := &environment.Flags{ PSWD: tc.Pwd, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PLAIN) path := &Path{ env: env, props: properties.Map{ + properties.Style: Full, MappedLocationsEnabled: false, MappedLocations: tc.MappedLocations, }, } - got := path.getFullPath() + path.setPath() + got := renderTemplate(env, "{{ .Path }}", path) assert.Equal(t, tc.Expected, got) } } -func TestNormalizePath(t *testing.T) { - cases := []struct { - Input string - GOOS string - Expected string - }{ - {Input: "C:\\Users\\Bob\\Foo", GOOS: environment.LINUX, Expected: "C:/Users/Bob/Foo/"}, - {Input: "C:\\Users\\Bob\\Foo", GOOS: environment.WINDOWS, Expected: "c:/users/bob/foo/"}, - {Input: "~\\Bob\\Foo", GOOS: environment.LINUX, Expected: "/usr/home/Bob/Foo/"}, - {Input: "~\\Bob\\Foo", GOOS: environment.WINDOWS, Expected: "/usr/home/bob/foo/"}, - {Input: "/foo/~/bar", GOOS: environment.LINUX, Expected: "/foo/~/bar/"}, - {Input: "/foo/~/bar", GOOS: environment.WINDOWS, Expected: "/foo/~/bar/"}, - {Input: "~/baz", GOOS: environment.LINUX, Expected: "/usr/home/baz/"}, - {Input: "~/baz", GOOS: environment.WINDOWS, Expected: "/usr/home/baz/"}, - } - - for _, tc := range cases { - env := new(mock.MockedEnvironment) - env.On("Home").Return("/usr/home") - env.On("GOOS").Return(tc.GOOS) - pt := &Path{ - env: env, - } - got := pt.normalize(tc.Input) - assert.Equal(t, tc.Expected, got) - } -} - -func TestGetFolderPathCustomMappedLocations(t *testing.T) { +func TestFolderPathCustomMappedLocations(t *testing.T) { pwd := "/a/b/c/d" env := new(mock.MockedEnvironment) env.On("PathSeparator").Return("/") - env.On("Home").Return("/usr/home") + env.On("Home").Return(homeDir) env.On("Pwd").Return(pwd) env.On("GOOS").Return("") args := &environment.Flags{ PSWD: pwd, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PLAIN) path := &Path{ env: env, props: properties.Map{ + properties.Style: Folder, MappedLocations: map[string]string{ "/a/b/c/d": "#", }, }, } - got := path.getFolderPath() + path.setPath() + got := renderTemplate(env, "{{ .Path }}", path) assert.Equal(t, "#", got) } @@ -532,7 +916,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows outside home", Expected: "C: > f > f > location", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Program Files\\Go\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -540,15 +924,15 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows oustide home", Expected: "~ > f > f > location", - Home: homeBillWindows, - PWD: homeBillWindows + "\\Documents\\Bill\\location", + Home: homeDirWindows, + PWD: homeDirWindows + "\\Documents\\Bill\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", }, { Case: "Windows inside home zero levels", Expected: "C: > location", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -556,7 +940,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows inside home one level", Expected: "C: > f > location", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Program Files\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -564,7 +948,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows lower case drive letter", Expected: "C: > Windows", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Windows\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -572,7 +956,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows lower case drive letter (other)", Expected: "P: > Other", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "P:\\Other\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -580,7 +964,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive", Expected: "some: > some", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "some:\\some\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -588,7 +972,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive (ending with c)", Expected: "src: > source", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "src:\\source\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -596,7 +980,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive (arbitrary cases)", Expected: "sRc: > source", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "sRc:\\source\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -604,7 +988,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows registry drive", Expected: "\uf013 > f > magnetic:test", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "HKLM:\\SOFTWARE\\magnetic:test\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -612,7 +996,7 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Windows registry drive case sensitive", Expected: "\uf013 > f > magnetic:TOAST", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -620,28 +1004,28 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl { Case: "Unix outside home", Expected: "mnt > f > f > location", - Home: homeJan, + Home: homeDir, PWD: "/mnt/go/test/location", PathSeparator: "/", }, { Case: "Unix inside home", Expected: "~ > f > f > location", - Home: homeJan, - PWD: homeJan + "/docs/jan/location", + Home: homeDir, + PWD: homeDir + "/docs/jan/location", PathSeparator: "/", }, { Case: "Unix outside home zero levels", Expected: "mnt > location", - Home: homeJan, + Home: homeDir, PWD: "/mnt/location", PathSeparator: "/", }, { Case: "Unix outside home one level", Expected: "mnt > f > location", - Home: homeJan, + Home: homeDir, PWD: "/mnt/folder/location", PathSeparator: "/", }, @@ -657,15 +1041,18 @@ func TestAgnosterPath(t *testing.T) { //nolint:dupl PSWD: tc.PWD, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) path := &Path{ env: env, props: properties.Map{ + properties.Style: Agnoster, FolderSeparatorIcon: " > ", FolderIcon: "f", HomeIcon: "~", }, } - got := path.getAgnosterPath() + path.setPath() + got := renderTemplate(env, "{{ .Path }}", path) assert.Equal(t, tc.Expected, got, tc.Case) } } @@ -682,7 +1069,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows outside home", Expected: "C: > Program Files > f > f", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Program Files\\Go\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -690,15 +1077,15 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows inside home", Expected: "~ > Documents > f > f", - Home: homeBillWindows, - PWD: homeBillWindows + "\\Documents\\Bill\\location", + Home: homeDirWindows, + PWD: homeDirWindows + "\\Documents\\Bill\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", }, { Case: "Windows inside home zero levels", Expected: "C: > location", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -706,7 +1093,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows inside home one level", Expected: "C: > Program Files > f", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Program Files\\location", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -714,7 +1101,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows lower case drive letter", Expected: "C: > Windows", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "C:\\Windows\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -722,7 +1109,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows lower case drive letter (other)", Expected: "P: > Other", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "P:\\Other\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -730,7 +1117,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive", Expected: "some: > some", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "some:\\some\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -738,7 +1125,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive (ending with c)", Expected: "src: > source", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "src:\\source\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -746,7 +1133,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows lower word drive (arbitrary cases)", Expected: "sRc: > source", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "sRc:\\source\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -754,7 +1141,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows registry drive", Expected: "\uf013 > SOFTWARE > f", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "HKLM:\\SOFTWARE\\magnetic:test\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -762,7 +1149,7 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Windows registry drive case sensitive", Expected: "\uf013 > SOFTWARE > f", - Home: homeBillWindows, + Home: homeDirWindows, PWD: "HKLM:\\SOFTWARE\\magnetic:TOAST\\", GOOS: environment.WINDOWS, PathSeparator: "\\", @@ -770,28 +1157,28 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl { Case: "Unix outside home", Expected: "mnt > go > f > f", - Home: homeJan, + Home: homeDir, PWD: "/mnt/go/test/location", PathSeparator: "/", }, { Case: "Unix inside home", Expected: "~ > docs > f > f", - Home: homeJan, - PWD: homeJan + "/docs/jan/location", + Home: homeDir, + PWD: homeDir + "/docs/jan/location", PathSeparator: "/", }, { Case: "Unix outside home zero levels", Expected: "mnt > location", - Home: homeJan, + Home: homeDir, PWD: "/mnt/location", PathSeparator: "/", }, { Case: "Unix outside home one level", Expected: "mnt > folder > f", - Home: homeJan, + Home: homeDir, PWD: "/mnt/folder/location", PathSeparator: "/", }, @@ -807,15 +1194,18 @@ func TestAgnosterLeftPath(t *testing.T) { //nolint:dupl PSWD: tc.PWD, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) path := &Path{ env: env, props: properties.Map{ + properties.Style: AgnosterLeft, FolderSeparatorIcon: " > ", FolderIcon: "f", HomeIcon: "~", }, } - got := path.getAgnosterLeftPath() + path.setPath() + got := renderTemplate(env, "{{ .Path }}", path) assert.Equal(t, tc.Expected, got, tc.Case) } } @@ -827,20 +1217,20 @@ func TestGetPwd(t *testing.T) { Pswd string Expected string }{ - {MappedLocationsEnabled: true, Pwd: "/usr/home", Expected: "~"}, - {MappedLocationsEnabled: true, Pwd: "/usr/home-test", Expected: "/usr/home-test"}, - {MappedLocationsEnabled: true, Pwd: "", Expected: ""}, + {MappedLocationsEnabled: true, Pwd: homeDir, Expected: "~"}, + {MappedLocationsEnabled: true, Pwd: homeDir + "-test", Expected: homeDir + "-test"}, + {MappedLocationsEnabled: true}, {MappedLocationsEnabled: true, Pwd: "/usr", Expected: "/usr"}, - {MappedLocationsEnabled: true, Pwd: "/usr/home/abc", Expected: "~/abc"}, + {MappedLocationsEnabled: true, Pwd: homeDir + "/abc", Expected: "~/abc"}, {MappedLocationsEnabled: true, Pwd: "/a/b/c/d", 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, Pwd: "", Expected: ""}, - {MappedLocationsEnabled: false, Pwd: "/usr/home/abc", Expected: "/usr/home/abc"}, + {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: "/usr/home/c/d/e/f/g", Expected: "/usr/home/c/d/e/f/g"}, - {MappedLocationsEnabled: true, Pwd: "/usr/home/c/d/e/f/g", Expected: "~/c/d/e/f/g"}, + {MappedLocationsEnabled: false, Pwd: homeDir + "/c/d/e/f/g", Expected: homeDir + "/c/d/e/f/g"}, + {MappedLocationsEnabled: true, Pwd: homeDir + "/c/d/e/f/g", 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"}, @@ -849,13 +1239,14 @@ func TestGetPwd(t *testing.T) { for _, tc := range cases { env := new(mock.MockedEnvironment) env.On("PathSeparator").Return("/") - env.On("Home").Return("/usr/home") + env.On("Home").Return(homeDir) env.On("Pwd").Return(tc.Pwd) env.On("GOOS").Return("") args := &environment.Flags{ PSWD: tc.Pswd, } env.On("Flags").Return(args) + env.On("Shell").Return(shell.PWSH) path := &Path{ env: env, props: properties.Map{ @@ -880,8 +1271,8 @@ func TestGetFolderSeparator(t *testing.T) { {Case: "default", Expected: "/"}, {Case: "icon - no template", FolderSeparatorIcon: "\ue5fe", Expected: "\ue5fe"}, {Case: "template", FolderSeparatorTemplate: "{{ if eq .Shell \"bash\" }}\\{{ end }}", Expected: "\\"}, - {Case: "template empty", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}\\{{ end }}", Expected: ""}, - {Case: "invalid template", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}", Expected: ""}, + {Case: "template empty", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}\\{{ end }}", Expected: "/"}, + {Case: "invalid template", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}", Expected: "/"}, } for _, tc := range cases { @@ -907,3 +1298,32 @@ func TestGetFolderSeparator(t *testing.T) { assert.Equal(t, tc.Expected, got) } } + +func TestNormalizePath(t *testing.T) { + cases := []struct { + Input string + HomeDir string + GOOS string + Expected string + }{ + {Input: homeDirWindows + "\\Foo", HomeDir: homeDirWindows, GOOS: environment.WINDOWS, Expected: "c:\\users\\someone\\foo"}, + {Input: "~/Bob\\Foo", HomeDir: homeDir, GOOS: environment.LINUX, Expected: homeDir + "/Bob\\Foo"}, + {Input: "~/Bob\\Foo", HomeDir: homeDir, GOOS: environment.DARWIN, Expected: homeDir + "/bob\\foo"}, + {Input: "~\\Bob\\Foo", HomeDir: homeDirWindows, GOOS: environment.WINDOWS, Expected: "c:\\users\\someone\\bob\\foo"}, + {Input: "/foo/~/bar", HomeDir: homeDir, GOOS: environment.LINUX, Expected: "/foo/~/bar"}, + {Input: "/foo/~/bar", HomeDir: homeDirWindows, GOOS: environment.WINDOWS, Expected: "\\foo\\~\\bar"}, + {Input: "~/baz", HomeDir: homeDir, GOOS: environment.LINUX, Expected: homeDir + "/baz"}, + {Input: "~/baz", HomeDir: homeDirWindows, GOOS: environment.WINDOWS, Expected: "c:\\users\\someone\\baz"}, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.On("Home").Return(tc.HomeDir) + env.On("GOOS").Return(tc.GOOS) + pt := &Path{ + env: env, + } + got := pt.normalize(tc.Input) + assert.Equal(t, tc.Expected, got) + } +} diff --git a/src/shell/scripts/omp.ps1 b/src/shell/scripts/omp.ps1 index 8069771d..335fb6c2 100644 --- a/src/shell/scripts/omp.ps1 +++ b/src/shell/scripts/omp.ps1 @@ -100,22 +100,22 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { function Set-PoshContext {} - function Get-PoshContext { - $cleanPWD = $PWD.ProviderPath - $cleanPSWD = $PWD.ToString() - $cleanPWD = $cleanPWD.TrimEnd('\') - $cleanPSWD = $cleanPSWD.TrimEnd('\') - return $cleanPWD, $cleanPSWD + function Get-CleanPSWD { + $pswd = $PWD.ToString() + if ($pswd -ne '/') { + return $pswd.TrimEnd('\') -replace '^Microsoft\.PowerShell\.Core\\FileSystem::', '' + } + return $pswd } if (("::TOOLTIPS::" -eq "true") -and ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")) { Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock { [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ') $position = $host.UI.RawUI.CursorPosition - $cleanPWD, $cleanPSWD = Get-PoshContext + $cleanPSWD = Get-CleanPSWD $command = $null [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null) - $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--error=$script:ErrorCode", "--pwd=$cleanPWD", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion")) + $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--error=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion")) Write-Host $standardOut -NoNewline $host.UI.RawUI.CursorPosition = $position # we need this workaround to prevent the text after cursor from disappearing when the tooltip is rendered @@ -259,9 +259,10 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { if ($List -eq $true) { $themes | Select-Object @{ Name = 'hyperlink'; Expression = { Get-FileHyperlink -uri $_.FullName } } | Format-Table -HideTableHeaders } else { + $cleanPSWD = Get-CleanPSWD $themes | ForEach-Object -Process { Write-Host "Theme: $(Get-FileHyperlink -uri $_.FullName -Name ($_.BaseName -replace '\.omp$', ''))`n" - @(Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pwd=$PWD", "--shell=$script:ShellName")) + @(Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName")) Write-Host "`n" } } @@ -331,7 +332,7 @@ Example: if ($script:PromptType -ne 'transient') { Update-PoshErrorCode } - $cleanPWD, $cleanPSWD = Get-PoshContext + $cleanPSWD = Get-CleanPSWD $stackCount = global:Get-PoshStackCount Set-PoshContext $terminalWidth = $Host.UI.RawUI.WindowSize.Width @@ -339,7 +340,7 @@ Example: if (-not $terminalWidth) { $terminalWidth = 0 } - $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--error=$script:ErrorCode", "--pwd=$cleanPWD", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName")) + $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--error=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName")) # make sure PSReadLine knows if we have a multiline prompt Set-PSReadLineOption -ExtraPromptLineCount (($standardOut | Measure-Object -Line).Lines - 1) # the output can be multiline, joining these ensures proper rendering by adding line breaks with `n diff --git a/website/docs/segments/path.mdx b/website/docs/segments/path.mdx index 821327d6..d727188b 100644 --- a/website/docs/segments/path.mdx +++ b/website/docs/segments/path.mdx @@ -42,9 +42,9 @@ Display the current path. ## Mapped Locations -Allows you to override a location with an icon. It validates if the current path **starts with** the value and replaces -it with the icon if there's a match. To avoid issues with nested overrides, Oh My Posh will sort the list of mapped -locations before doing a replacement. +Allows you to override a location with an icon/string. +It validates if the current path **starts with the specific elements** and replaces it with the icon/string if there's a match. +To avoid issues with nested overrides, Oh My Posh will sort the list of mapped locations before doing a replacement. | Name | Type | Description | | -------------------------- | --------- | -------------------------------------------------------------------------------------------------------- | @@ -63,8 +63,8 @@ For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do t ### Notes -- Oh My Posh will accept both `/` and `\` as path separators for a mapped location and will match regardless of which - is used by the current operating system. +- To make mapped Locations work cross-platform, you should use `/` as the path separator, Oh My Posh will +automatically match effective separators based on the running operating system. - The character `~` at the start of a mapped location will match the user's home directory. - The match is case-insensitive on Windows and macOS, but case-sensitive on other operating systems. @@ -87,8 +87,8 @@ Style sets the way the path is displayed. Based on previous experience and popul ### Agnoster -Renders each folder as the `folder_icon` separated by the `folder_separator_icon`. -Only the current folder name is displayed at the end. +Renders each intermediate folder as the `folder_icon` separated by the `folder_separator_icon`. +The first and the last (current) folder name are always displayed as-is. ### Agnoster Full @@ -96,17 +96,18 @@ Renders each folder name separated by the `folder_separator_icon`. ### Agnoster Short -When more than `max_depth` levels deep, it renders one `folder_icon` (if `hide_root_location` is `false`) followed by -the names of the last `max_depth` folders, separated by the `folder_separator_icon`. +When more than `max_depth` levels deep, it renders one `folder_icon` (if `hide_root_location` is `false`, +which means the root folder does not count for depth) followed by the names of the last `max_depth` folders, +separated by the `folder_separator_icon`. ### Agnoster Left Renders each folder as the `folder_icon` separated by the `folder_separator_icon`. -Only the root folder name and it's child are displayed in full. +Only the first folder name and its child are displayed in full. ### Full -Display `$PWD` as a string. +Display the current working directory as a full string with each folder separated by the `folder_separator_icon`. ### Folder @@ -114,13 +115,13 @@ Display the name of the current folder. ### Mixed -Works like `Agnoster Full`, but for any middle folder short enough it will display its name instead. The maximum length -for the folders to display is governed by the `mixed_threshold` property. +Works like `Agnoster`, but for any intermediate folder name that is short enough, it will be displayed as-is. +The maximum length for the folders to display is governed by the `mixed_threshold` property. ### Letter -Works like `Full`, but will write every subfolder name using the first letter only, except when the folder name -starts with a symbol or icon. +Works like `Agnoster Full`, but will write every folder name using the first letter only, except when the folder name +starts with a symbol or icon. Specially, the last (current) folder name is always displayed in full. - `folder` will be shortened to `f` - `.config` will be shortened to `.c` @@ -132,9 +133,9 @@ starts with a symbol or icon. Works like `Letter`, but will make sure every folder name is the shortest unique value. The uniqueness refers to the displayed path, so `C:\dev\dev\dev\development` will be displayed as -`C:\d\de\dev\development` (instead of `C:\d\d\d\development` for `Letter`). Uniqueness does _not_ refer to other +`C\d\de\dev\development` (instead of `C\d\d\d\development` for `Letter`). Uniqueness does **not** refer to other folders at the same level, so if `C:\projectA\dev` and `C:\projectB\dev` exist, then both will be displayed as -`C:\p\dev`. +`C\p\dev`. ## Template ([info][templates]) @@ -148,12 +149,12 @@ folders at the same level, so if `C:\projectA\dev` and `C:\projectB\dev` exist, ### Properties -| Name | Type | Description | +| Name | Type | Description | | ------------- | --------- | ---------------------------------------------------------------------------- | -| `.Path` | `string` | the current directory (based on the `style` property) | -| `.Parent` | `string` | the current directory's parent folder (designed for use with style `folder`) | -| `.Location` | `string` | the current directory (raw value) | -| `.StackCount` | `int` | the stack count | -| `.Writable` | `boolean` | is the current directory writable by the user or not | +| `.Path` | `string` | the current directory (based on the `style` property) | +| `.Parent` | `string` | the current directory's parent folder which ends with a path separator (designed for use with style `folder`, it is empty if `.Path` contains only one single element) | +| `.Location` | `string` | the current directory (raw value) | +| `.StackCount` | `int` | the stack count | +| `.Writable` | `boolean` | is the current directory writable by the user or not | [templates]: /docs/configuration/templates