mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-11-09 20:44:03 -08:00
fix(path): improve path cleaning, normalization and parsing
This commit is contained in:
parent
476bfd1fff
commit
abd6676c5b
|
@ -171,7 +171,7 @@ func (e *Engine) getTitleTemplateText() string {
|
|||
}
|
||||
|
||||
func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
|
||||
defer e.patchPowerShellBleed()
|
||||
defer e.applyPowerShellBleedPatch()
|
||||
|
||||
// This is deprecated but we leave it in to not break configs
|
||||
// It is encouraged to use "newline": true on block level
|
||||
|
@ -267,7 +267,7 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (e *Engine) patchPowerShellBleed() {
|
||||
func (e *Engine) applyPowerShellBleedPatch() {
|
||||
// when in PowerShell, we need to clear the line after the prompt
|
||||
// to avoid the background being printed on the next line
|
||||
// when at the end of the buffer.
|
||||
|
@ -514,10 +514,6 @@ func New(flags *runtime.Flags) *Engine {
|
|||
env.Init()
|
||||
cfg := config.Load(env)
|
||||
|
||||
if cfg.PatchPwshBleed {
|
||||
patchPowerShellBleed(env.Shell(), flags)
|
||||
}
|
||||
|
||||
env.Var = cfg.Var
|
||||
flags.HasTransient = cfg.TransientPrompt != nil
|
||||
|
||||
|
@ -532,10 +528,16 @@ func New(flags *runtime.Flags) *Engine {
|
|||
Plain: flags.Plain,
|
||||
}
|
||||
|
||||
if cfg.PatchPwshBleed {
|
||||
eng.patchPowerShellBleed()
|
||||
}
|
||||
|
||||
return eng
|
||||
}
|
||||
|
||||
func patchPowerShellBleed(sh string, flags *runtime.Flags) {
|
||||
func (e *Engine) patchPowerShellBleed() {
|
||||
sh := e.Env.Shell()
|
||||
|
||||
// when in PowerShell, and force patching the bleed bug
|
||||
// we need to reduce the terminal width by 1 so the last
|
||||
// character isn't cut off by the ANSI escape sequences
|
||||
|
@ -544,10 +546,12 @@ func patchPowerShellBleed(sh string, flags *runtime.Flags) {
|
|||
return
|
||||
}
|
||||
|
||||
// only do this when relevant
|
||||
if flags.TerminalWidth <= 0 {
|
||||
// Since the terminal width may not be given by the CLI flag, we should always call this here.
|
||||
_, err := e.Env.TerminalWidth()
|
||||
if err != nil {
|
||||
// Skip when we're unable to determine the terminal width.
|
||||
return
|
||||
}
|
||||
|
||||
flags.TerminalWidth--
|
||||
e.Env.Flags().TerminalWidth--
|
||||
}
|
||||
|
|
|
@ -173,25 +173,20 @@ func (term *Terminal) Pwd() string {
|
|||
if term.cwd != "" {
|
||||
return term.cwd
|
||||
}
|
||||
correctPath := func(pwd string) string {
|
||||
if term.GOOS() != WINDOWS {
|
||||
return pwd
|
||||
}
|
||||
// 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 term.CmdFlags != nil && term.CmdFlags.PWD != "" {
|
||||
term.cwd = correctPath(term.CmdFlags.PWD)
|
||||
term.cwd = CleanPath(term, term.CmdFlags.PWD)
|
||||
term.Debug(term.cwd)
|
||||
return term.cwd
|
||||
}
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
term.Error(err)
|
||||
return ""
|
||||
}
|
||||
term.cwd = correctPath(dir)
|
||||
|
||||
term.cwd = CleanPath(term, dir)
|
||||
term.Debug(term.cwd)
|
||||
return term.cwd
|
||||
}
|
||||
|
@ -321,7 +316,10 @@ func (term *Terminal) LsDir(path string) []fs.DirEntry {
|
|||
|
||||
func (term *Terminal) PathSeparator() string {
|
||||
defer term.Trace(time.Now())
|
||||
return string(os.PathSeparator)
|
||||
if term.GOOS() == WINDOWS {
|
||||
return `\`
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
|
||||
func (term *Terminal) User() string {
|
||||
|
@ -882,6 +880,54 @@ func Base(env Environment, path string) string {
|
|||
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
|
||||
|
|
|
@ -13,23 +13,44 @@ import (
|
|||
"github.com/jandedobbeleer/oh-my-posh/src/template"
|
||||
)
|
||||
|
||||
type Folder struct {
|
||||
Name string
|
||||
Display bool
|
||||
Path string
|
||||
}
|
||||
|
||||
type Folders []*Folder
|
||||
|
||||
func (f Folders) List() []string {
|
||||
var list []string
|
||||
|
||||
for _, folder := range f {
|
||||
list = append(list, folder.Name)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
props properties.Properties
|
||||
env runtime.Environment
|
||||
|
||||
root string
|
||||
relative string
|
||||
pwd string
|
||||
cygPath bool
|
||||
windowsPath bool
|
||||
pathSeparator string
|
||||
pwd string
|
||||
root string
|
||||
relative string
|
||||
folders Folders
|
||||
// After `setPaths` is called, the above 4 fields should remain unchanged to preserve the original path info.
|
||||
|
||||
cygPath bool
|
||||
windowsPath bool
|
||||
pathSeparator string
|
||||
mappedLocations map[string]string
|
||||
|
||||
Path string
|
||||
StackCount int
|
||||
Location string
|
||||
Writable bool
|
||||
RootDir bool
|
||||
Folders Folders
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -121,7 +142,7 @@ func (pt *Path) Enabled() bool {
|
|||
|
||||
func (pt *Path) setPaths() {
|
||||
defer func() {
|
||||
pt.Folders = pt.splitPath()
|
||||
pt.folders = pt.splitPath()
|
||||
}()
|
||||
|
||||
displayCygpath := func() bool {
|
||||
|
@ -147,33 +168,33 @@ func (pt *Path) setPaths() {
|
|||
}
|
||||
|
||||
// ensure a clean path
|
||||
pt.root, pt.relative = pt.replaceMappedLocations()
|
||||
|
||||
// this is a full replacement of the parent
|
||||
if len(pt.root) == 0 {
|
||||
pt.pwd = pt.relative
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(pt.root, pt.pathSeparator) && len(pt.relative) > 0 {
|
||||
pt.pwd = pt.root + pt.pathSeparator + pt.relative
|
||||
return
|
||||
}
|
||||
|
||||
pt.pwd = pt.root + pt.relative
|
||||
pt.root, pt.relative = pt.replaceMappedLocations(pt.pwd)
|
||||
pt.pwd = pt.join(pt.root, pt.relative)
|
||||
}
|
||||
|
||||
func (pt *Path) Parent() string {
|
||||
if len(pt.pwd) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(pt.relative) == 0 {
|
||||
// a root path has no parent
|
||||
|
||||
folders := pt.folders.List()
|
||||
if len(folders) == 0 {
|
||||
// No parent.
|
||||
return ""
|
||||
}
|
||||
base := runtime.Base(pt.env, pt.pwd)
|
||||
path := pt.replaceFolderSeparators(pt.pwd[:len(pt.pwd)-len(base)])
|
||||
return path
|
||||
|
||||
sb := new(strings.Builder)
|
||||
folderSeparator := pt.getFolderSeparator()
|
||||
|
||||
sb.WriteString(pt.root)
|
||||
if !pt.endWithSeparator(pt.root) {
|
||||
sb.WriteString(folderSeparator)
|
||||
}
|
||||
for _, folder := range folders[:len(folders)-1] {
|
||||
sb.WriteString(folder)
|
||||
sb.WriteString(folderSeparator)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (pt *Path) Init(props properties.Properties, env runtime.Environment) {
|
||||
|
@ -183,12 +204,14 @@ func (pt *Path) Init(props properties.Properties, env runtime.Environment) {
|
|||
|
||||
func (pt *Path) setStyle() {
|
||||
if len(pt.relative) == 0 {
|
||||
root := pt.root
|
||||
|
||||
// Only append a separator to a non-filesystem PSDrive root or a Windows drive root.
|
||||
if (len(pt.env.Flags().PSWD) != 0 || pt.windowsPath) && strings.HasSuffix(pt.root, ":") {
|
||||
pt.root += pt.getFolderSeparator()
|
||||
if (len(pt.env.Flags().PSWD) != 0 || pt.windowsPath) && strings.HasSuffix(root, ":") {
|
||||
root += pt.getFolderSeparator()
|
||||
}
|
||||
|
||||
pt.Path = pt.colorizePath(pt.root, nil)
|
||||
pt.Path = pt.colorizePath(root, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -276,75 +299,77 @@ func (pt *Path) getFolderSeparator() string {
|
|||
}
|
||||
|
||||
func (pt *Path) getMixedPath() string {
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
threshold := int(pt.props.GetFloat64(MixedThreshold, 4))
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
var folders []string
|
||||
var elements []string
|
||||
|
||||
for i, n := 0, len(pt.Folders); i < n; i++ {
|
||||
folder := pt.Folders[i].Name
|
||||
if len(folder) > threshold && i != n-1 && !pt.Folders[i].Display {
|
||||
folder = folderIcon
|
||||
for i, n := 0, len(folders); i < n; i++ {
|
||||
folderName := folders[i].Name
|
||||
if len(folderName) > threshold && i != n-1 && !folders[i].Display {
|
||||
elements = append(elements, folderIcon)
|
||||
continue
|
||||
}
|
||||
folders = append(folders, folder)
|
||||
|
||||
elements = append(elements, folderName)
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, folders)
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterPath() string {
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
var elements []string
|
||||
n := len(pt.Folders)
|
||||
for i := 0; i < n-1; i++ {
|
||||
name := folderIcon
|
||||
|
||||
if pt.Folders[i].Display {
|
||||
name = pt.Folders[i].Name
|
||||
}
|
||||
|
||||
elements = append(elements, name)
|
||||
}
|
||||
|
||||
if len(pt.Folders) > 0 {
|
||||
elements = append(elements, pt.Folders[n-1].Name)
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterLeftPath() string {
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
}
|
||||
|
||||
var elements []string
|
||||
n := len(pt.Folders)
|
||||
elements = append(elements, pt.Folders[0].Name)
|
||||
for i := 1; i < n; i++ {
|
||||
if pt.Folders[i].Display {
|
||||
elements = append(elements, pt.Folders[i].Name)
|
||||
for i, n := 0, len(folders); i < n; i++ {
|
||||
if folders[i].Display || i == n-1 {
|
||||
elements = append(elements, folders[i].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
elements = append(elements, folderIcon)
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, elements)
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterLeftPath() string {
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
var elements []string
|
||||
elements = append(elements, folders[0].Name)
|
||||
for i, n := 1, len(folders); i < n; i++ {
|
||||
if folders[i].Display {
|
||||
elements = append(elements, folders[i].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
elements = append(elements, folderIcon)
|
||||
}
|
||||
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getRelevantLetter(folder *Folder) string {
|
||||
|
@ -365,62 +390,78 @@ func (pt *Path) getRelevantLetter(folder *Folder) string {
|
|||
}
|
||||
|
||||
func (pt *Path) getLetterPath() string {
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
pt.root = pt.getRelevantLetter(&Folder{Name: pt.root})
|
||||
root = pt.getRelevantLetter(&Folder{Name: root})
|
||||
|
||||
var elements []string
|
||||
n := len(pt.Folders)
|
||||
for i := 0; i < n-1; i++ {
|
||||
if pt.Folders[i].Display {
|
||||
elements = append(elements, pt.Folders[i].Name)
|
||||
for i, n := 0, len(folders); i < n; i++ {
|
||||
if folders[i].Display || i == n-1 {
|
||||
elements = append(elements, folders[i].Name)
|
||||
continue
|
||||
}
|
||||
|
||||
letter := pt.getRelevantLetter(pt.Folders[i])
|
||||
letter := pt.getRelevantLetter(folders[i])
|
||||
elements = append(elements, letter)
|
||||
}
|
||||
|
||||
if len(pt.Folders) > 0 {
|
||||
elements = append(elements, pt.Folders[n-1].Name)
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, elements)
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getUniqueLettersPath(maxWidth int) string {
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
separator := pt.getFolderSeparator()
|
||||
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
folderNames := folders.List()
|
||||
|
||||
usePowerlevelStyle := func(root, relative string) bool {
|
||||
length := len(root) + len(relative)
|
||||
if !pt.endWithSeparator(root) {
|
||||
length += len(separator)
|
||||
}
|
||||
return length <= maxWidth
|
||||
}
|
||||
|
||||
if maxWidth > 0 {
|
||||
path := strings.Join(pt.Folders.List(), separator)
|
||||
if len(path) <= maxWidth {
|
||||
return pt.colorizePath(pt.root, pt.Folders.List())
|
||||
relative := strings.Join(folderNames, separator)
|
||||
if usePowerlevelStyle(root, relative) {
|
||||
return pt.colorizePath(root, folderNames)
|
||||
}
|
||||
}
|
||||
|
||||
pt.root = pt.getRelevantLetter(&Folder{Name: pt.root})
|
||||
root = pt.getRelevantLetter(&Folder{Name: root})
|
||||
|
||||
var elements []string
|
||||
n := len(pt.Folders)
|
||||
letters := make(map[string]bool)
|
||||
letters[pt.root] = true
|
||||
for i := 0; i < n-1; i++ {
|
||||
folder := pt.Folders[i].Name
|
||||
letter := pt.getRelevantLetter(pt.Folders[i])
|
||||
letters[root] = true
|
||||
|
||||
for i, n := 0, len(folders); i < n; i++ {
|
||||
folderName := folderNames[i]
|
||||
|
||||
if i == n-1 {
|
||||
elements = append(elements, folderName)
|
||||
break
|
||||
}
|
||||
|
||||
letter := pt.getRelevantLetter(folders[i])
|
||||
|
||||
for letters[letter] {
|
||||
if letter == folder {
|
||||
if letter == folderName {
|
||||
break
|
||||
}
|
||||
letter += folder[len(letter) : len(letter)+1]
|
||||
letter += folderName[len(letter) : len(letter)+1]
|
||||
}
|
||||
|
||||
letters[letter] = true
|
||||
|
@ -429,87 +470,95 @@ func (pt *Path) getUniqueLettersPath(maxWidth int) string {
|
|||
// only return early on maxWidth > 0
|
||||
// this enables the powerlevel10k behavior
|
||||
if maxWidth > 0 {
|
||||
list := pt.Folders[i+1:].List()
|
||||
list = append(list, elements...)
|
||||
current := strings.Join(list, separator)
|
||||
leftover := maxWidth - len(current) - len(pt.root) - len(separator)
|
||||
if leftover >= 0 {
|
||||
elements = append(elements, strings.Join(pt.Folders[i+1:].List(), separator))
|
||||
return pt.colorizePath(pt.root, elements)
|
||||
list := elements
|
||||
list = append(list, folderNames[i+1:]...)
|
||||
relative := strings.Join(list, separator)
|
||||
if usePowerlevelStyle(root, relative) {
|
||||
return pt.colorizePath(root, list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(pt.Folders) > 0 {
|
||||
elements = append(elements, pt.Folders[n-1].Name)
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, elements)
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterFullPath() string {
|
||||
if pt.root == pt.pathSeparator {
|
||||
pt.root = pt.Folders[0].Name
|
||||
pt.Folders = pt.Folders[1:]
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
return pt.colorizePath(pt.root, pt.Folders.List())
|
||||
return pt.colorizePath(root, folders.List())
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterShortPath() string {
|
||||
pathDepth := len(pt.Folders)
|
||||
root := pt.root
|
||||
folders := pt.folders
|
||||
|
||||
if pt.isRootFS(root) {
|
||||
root = folders[0].Name
|
||||
folders = folders[1:]
|
||||
}
|
||||
|
||||
maxDepth := pt.props.GetInt(MaxDepth, 1)
|
||||
if maxDepth < 1 {
|
||||
maxDepth = 1
|
||||
}
|
||||
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
pathDepth := len(folders)
|
||||
hideRootLocation := pt.props.GetBool(HideRootLocation, false)
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
|
||||
if pathDepth <= maxDepth {
|
||||
if hideRootLocation {
|
||||
pt.root = folderIcon
|
||||
}
|
||||
// No need to shorten.
|
||||
if pathDepth < maxDepth || (pathDepth == maxDepth && !hideRootLocation) {
|
||||
return pt.getAgnosterFullPath()
|
||||
}
|
||||
|
||||
splitPos := pathDepth - maxDepth
|
||||
elements := []string{folderIcon}
|
||||
|
||||
var folders []string
|
||||
// unix root, needs to be replaced with the folder we're in at root level
|
||||
root := pt.root
|
||||
room := pathDepth - maxDepth
|
||||
if root == pt.pathSeparator {
|
||||
root = pt.Folders[0].Name
|
||||
room--
|
||||
}
|
||||
|
||||
if hideRootLocation || room > 0 {
|
||||
folders = append(folders, folderIcon)
|
||||
for i := pathDepth - maxDepth; i < pathDepth; i++ {
|
||||
elements = append(elements, folders[i].Name)
|
||||
}
|
||||
|
||||
if hideRootLocation {
|
||||
root = ""
|
||||
return pt.colorizePath(elements[0], elements[1:])
|
||||
}
|
||||
|
||||
for i := splitPos; i < pathDepth; i++ {
|
||||
folders = append(folders, pt.Folders[i].Name)
|
||||
}
|
||||
|
||||
return pt.colorizePath(root, folders)
|
||||
return pt.colorizePath(root, elements)
|
||||
}
|
||||
|
||||
func (pt *Path) getFullPath() string {
|
||||
return pt.colorizePath(pt.root, pt.Folders.List())
|
||||
return pt.colorizePath(pt.root, pt.folders.List())
|
||||
}
|
||||
|
||||
func (pt *Path) getFolderPath() string {
|
||||
return pt.colorizePath(runtime.Base(pt.env, pt.pwd), nil)
|
||||
folderName := pt.folders[len(pt.folders)-1].Name
|
||||
return pt.colorizePath(folderName, nil)
|
||||
}
|
||||
|
||||
func (pt *Path) replaceMappedLocations() (string, string) {
|
||||
mappedLocations := map[string]string{}
|
||||
func (pt *Path) join(root, relative string) string {
|
||||
// this is a full replacement of the parent
|
||||
if len(root) == 0 {
|
||||
return relative
|
||||
}
|
||||
|
||||
if !pt.endWithSeparator(root) && len(relative) > 0 {
|
||||
return root + pt.pathSeparator + relative
|
||||
}
|
||||
|
||||
return root + relative
|
||||
}
|
||||
|
||||
func (pt *Path) setMappedLocations() {
|
||||
if pt.mappedLocations != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mappedLocations := make(map[string]string)
|
||||
|
||||
// predefined mapped locations, can be disabled
|
||||
if pt.props.GetBool(MappedLocationsEnabled, true) {
|
||||
mappedLocations["hkcu:"] = pt.props.GetString(WindowsRegistryIcon, "\uF013")
|
||||
|
@ -520,7 +569,7 @@ func (pt *Path) replaceMappedLocations() (string, string) {
|
|||
// merge custom locations with mapped locations
|
||||
// mapped locations can override predefined locations
|
||||
keyValues := pt.props.GetKeyValueMap(MappedLocations, make(map[string]string))
|
||||
for key, val := range keyValues {
|
||||
for key, value := range keyValues {
|
||||
if len(key) == 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -540,89 +589,86 @@ func (pt *Path) replaceMappedLocations() (string, string) {
|
|||
continue
|
||||
}
|
||||
|
||||
mappedLocations[pt.normalize(path)] = val
|
||||
// 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 {
|
||||
continue
|
||||
}
|
||||
|
||||
mappedLocations[pt.normalize(path)] = value
|
||||
}
|
||||
|
||||
pt.mappedLocations = mappedLocations
|
||||
}
|
||||
|
||||
func (pt *Path) replaceMappedLocations(inputPath string) (string, string) {
|
||||
root, relative := pt.parsePath(inputPath)
|
||||
if len(relative) == 0 {
|
||||
pt.RootDir = true
|
||||
}
|
||||
|
||||
pt.setMappedLocations()
|
||||
if len(pt.mappedLocations) == 0 {
|
||||
return root, relative
|
||||
}
|
||||
|
||||
// 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, 0, len(mappedLocations))
|
||||
for k := range mappedLocations {
|
||||
keys := make([]string, 0, len(pt.mappedLocations))
|
||||
for k := range pt.mappedLocations {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
|
||||
|
||||
root, relative := pt.parsePath(pt.pwd)
|
||||
if len(relative) == 0 {
|
||||
pt.RootDir = true
|
||||
}
|
||||
|
||||
rootN := pt.normalize(root)
|
||||
relativeN := pt.normalize(relative)
|
||||
|
||||
escape := func(path string) string {
|
||||
// Escape chevron characters to avoid applying unexpected text styles.
|
||||
return strings.NewReplacer("<", "<<>", ">", "<>>").Replace(path)
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
keyRoot, keyRelative := pt.parsePath(key)
|
||||
matchSubFolders := strings.HasSuffix(keyRelative, "*")
|
||||
matchSubFolders := strings.HasSuffix(keyRelative, pt.pathSeparator+"*")
|
||||
|
||||
if matchSubFolders && len(keyRelative) > 1 {
|
||||
keyRelative = keyRelative[0 : len(keyRelative)-1] // remove trailing /* or \*
|
||||
if matchSubFolders {
|
||||
// Remove the trailing wildcard (*).
|
||||
keyRelative = keyRelative[:len(keyRelative)-1]
|
||||
}
|
||||
|
||||
if keyRoot != rootN || !strings.HasPrefix(relativeN, keyRelative) {
|
||||
continue
|
||||
}
|
||||
|
||||
value := mappedLocations[key]
|
||||
value := pt.mappedLocations[key]
|
||||
overflow := relative[len(keyRelative):]
|
||||
|
||||
// exactly match the full path
|
||||
if len(overflow) == 0 {
|
||||
// exactly match the full path
|
||||
return value, ""
|
||||
}
|
||||
|
||||
// only match the root
|
||||
if len(keyRelative) == 0 {
|
||||
// only match the root
|
||||
return value, strings.Trim(relative, pt.pathSeparator)
|
||||
return value, strings.Trim(escape(relative), pt.pathSeparator)
|
||||
}
|
||||
|
||||
// match several prefix elements
|
||||
if matchSubFolders || overflow[0:1] == pt.pathSeparator {
|
||||
return value, strings.Trim(overflow, pt.pathSeparator)
|
||||
if matchSubFolders || overflow[:1] == pt.pathSeparator {
|
||||
return value, strings.Trim(escape(overflow), pt.pathSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
return root, strings.Trim(relative, pt.pathSeparator)
|
||||
return escape(root), strings.Trim(escape(relative), pt.pathSeparator)
|
||||
}
|
||||
|
||||
func (pt *Path) normalizePath(path string) string {
|
||||
if pt.env.GOOS() != runtime.WINDOWS || pt.cygPath {
|
||||
return path
|
||||
}
|
||||
|
||||
var clean []rune
|
||||
for _, char := range path {
|
||||
var lastChar rune
|
||||
if len(clean) > 0 {
|
||||
lastChar = clean[len(clean)-1:][0]
|
||||
}
|
||||
|
||||
if char == '/' && lastChar != 60 { // 60 == <, this is done to avoid replacing color codes
|
||||
clean = append(clean, 92) // 92 == \
|
||||
continue
|
||||
}
|
||||
|
||||
clean = append(clean, char)
|
||||
}
|
||||
|
||||
return string(clean)
|
||||
}
|
||||
|
||||
// ParsePath parses an input path and returns a clean root and a clean path.
|
||||
// parsePath parses a clean input path into a root and a relative.
|
||||
func (pt *Path) parsePath(inputPath string) (string, string) {
|
||||
var root, path string
|
||||
var root, relative string
|
||||
|
||||
if len(inputPath) == 0 {
|
||||
return root, path
|
||||
return root, relative
|
||||
}
|
||||
|
||||
if pt.cygPath {
|
||||
|
@ -638,76 +684,58 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
|
|||
}
|
||||
}
|
||||
|
||||
clean := func(path string) string {
|
||||
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, pt.pathSeparator), path)
|
||||
n := len(matches) - 1
|
||||
s := new(strings.Builder)
|
||||
for i, m := range matches {
|
||||
s.WriteString(m["element"])
|
||||
if i != n {
|
||||
s.WriteString(pt.pathSeparator)
|
||||
}
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
if pt.windowsPath {
|
||||
inputPath = pt.normalizePath(inputPath)
|
||||
// for a UNC path, extract \\hostname\sharename as the root
|
||||
matches := regex.FindNamedRegexMatch(`^\\\\(?P<hostname>[^\\]+)\\+(?P<sharename>[^\\]+)\\*(?P<path>[\s\S]*)$`, inputPath)
|
||||
if pt.env.GOOS() == runtime.WINDOWS {
|
||||
// Handle a UNC path, if any.
|
||||
pattern := fmt.Sprintf(`^\%[1]s{2}(?P<hostname>[^\%[1]s]+)\%[1]s(?P<sharename>[^\%[1]s]+)(\%[1]s(?P<path>[\s\S]*))?$`, pt.pathSeparator)
|
||||
matches := regex.FindNamedRegexMatch(pattern, inputPath)
|
||||
if len(matches) > 0 {
|
||||
root = `\\` + matches["hostname"] + `\` + matches["sharename"]
|
||||
path = clean(matches["path"])
|
||||
return root, path
|
||||
root = fmt.Sprintf(`%[1]s%[1]s%[2]s%[1]s%[3]s`, pt.pathSeparator, matches["hostname"], matches["sharename"])
|
||||
relative = matches["path"]
|
||||
return root, relative
|
||||
}
|
||||
}
|
||||
|
||||
s := strings.SplitAfterN(inputPath, pt.pathSeparator, 2)
|
||||
root = s[0]
|
||||
|
||||
if pt.windowsPath {
|
||||
root = strings.TrimSuffix(root, pt.pathSeparator)
|
||||
}
|
||||
|
||||
if len(s) == 2 {
|
||||
path = clean(s[1])
|
||||
if len(root) > 1 {
|
||||
root = root[:len(root)-1]
|
||||
}
|
||||
|
||||
relative = s[1]
|
||||
}
|
||||
|
||||
return root, path
|
||||
return root, relative
|
||||
}
|
||||
|
||||
func (pt *Path) isRootFS(path string) bool {
|
||||
return len(path) == 1 && runtime.IsPathSeparator(pt.env, path[0])
|
||||
}
|
||||
|
||||
func (pt *Path) endWithSeparator(path string) bool {
|
||||
if len(path) == 0 {
|
||||
return false
|
||||
}
|
||||
return runtime.IsPathSeparator(pt.env, path[len(path)-1])
|
||||
}
|
||||
|
||||
func (pt *Path) normalize(inputPath string) string {
|
||||
normalized := inputPath
|
||||
|
||||
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || runtime.IsPathSeparator(pt.env, normalized[1])) {
|
||||
normalized = pt.env.Home() + normalized[1:]
|
||||
}
|
||||
|
||||
if pt.cygPath {
|
||||
return normalized
|
||||
}
|
||||
normalized = runtime.CleanPath(pt.env, normalized)
|
||||
|
||||
switch pt.env.GOOS() {
|
||||
case runtime.WINDOWS:
|
||||
normalized = pt.normalizePath(normalized)
|
||||
fallthrough
|
||||
case runtime.DARWIN:
|
||||
if pt.env.GOOS() == runtime.WINDOWS || pt.env.GOOS() == runtime.DARWIN {
|
||||
normalized = strings.ToLower(normalized)
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
func (pt *Path) replaceFolderSeparators(pwd string) string {
|
||||
defaultSeparator := pt.pathSeparator
|
||||
folderSeparator := pt.getFolderSeparator()
|
||||
if folderSeparator == defaultSeparator {
|
||||
return pwd
|
||||
}
|
||||
|
||||
pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator)
|
||||
return pwd
|
||||
}
|
||||
|
||||
func (pt *Path) colorizePath(root string, elements []string) string {
|
||||
cycle := pt.props.GetStringArray(Cycle, []string{})
|
||||
skipColorize := len(cycle) == 0
|
||||
|
@ -730,8 +758,8 @@ func (pt *Path) colorizePath(root string, elements []string) string {
|
|||
}
|
||||
|
||||
if len(elements) == 0 {
|
||||
root = fmt.Sprintf(leftFormat, root)
|
||||
return colorizeElement(root)
|
||||
formattedRoot := fmt.Sprintf(leftFormat, root)
|
||||
return colorizeElement(formattedRoot)
|
||||
}
|
||||
|
||||
colorizeSeparator := func() string {
|
||||
|
@ -741,13 +769,13 @@ func (pt *Path) colorizePath(root string, elements []string) string {
|
|||
return fmt.Sprintf("<%s>%s</>", cycle[0], folderSeparator)
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
sb := new(strings.Builder)
|
||||
|
||||
formattedRoot := fmt.Sprintf(leftFormat, root)
|
||||
builder.WriteString(colorizeElement(formattedRoot))
|
||||
sb.WriteString(colorizeElement(formattedRoot))
|
||||
|
||||
if root != pt.pathSeparator && len(root) != 0 {
|
||||
builder.WriteString(colorizeSeparator())
|
||||
if !pt.endWithSeparator(root) {
|
||||
sb.WriteString(colorizeSeparator())
|
||||
}
|
||||
|
||||
for i, element := range elements {
|
||||
|
@ -760,76 +788,49 @@ func (pt *Path) colorizePath(root string, elements []string) string {
|
|||
format = rightFormat
|
||||
}
|
||||
|
||||
element = fmt.Sprintf(format, element)
|
||||
builder.WriteString(colorizeElement(element))
|
||||
formattedElement := fmt.Sprintf(format, element)
|
||||
sb.WriteString(colorizeElement(formattedElement))
|
||||
if i != len(elements)-1 {
|
||||
builder.WriteString(colorizeSeparator())
|
||||
sb.WriteString(colorizeSeparator())
|
||||
}
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
type Folder struct {
|
||||
Name string
|
||||
Display bool
|
||||
Path string
|
||||
}
|
||||
|
||||
type Folders []*Folder
|
||||
|
||||
func (f Folders) List() []string {
|
||||
var list []string
|
||||
|
||||
for _, folder := range f {
|
||||
list = append(list, folder.Name)
|
||||
}
|
||||
|
||||
return list
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (pt *Path) splitPath() Folders {
|
||||
result := Folders{}
|
||||
folders := []string{}
|
||||
folders := Folders{}
|
||||
|
||||
if len(pt.relative) != 0 {
|
||||
folders = strings.Split(pt.relative, pt.pathSeparator)
|
||||
if len(pt.relative) == 0 {
|
||||
return folders
|
||||
}
|
||||
|
||||
elements := strings.Split(pt.relative, pt.pathSeparator)
|
||||
folderFormatMap := pt.makeFolderFormatMap()
|
||||
currentPath := pt.root
|
||||
|
||||
getCurrentPath := func() string {
|
||||
if pt.root == "~" {
|
||||
return pt.env.Home() + pt.pathSeparator
|
||||
}
|
||||
|
||||
if pt.windowsPath {
|
||||
return pt.root + pt.pathSeparator
|
||||
}
|
||||
|
||||
return pt.root
|
||||
if !pt.endWithSeparator(pt.root) {
|
||||
currentPath += pt.pathSeparator
|
||||
}
|
||||
|
||||
currentPath := getCurrentPath()
|
||||
|
||||
var display bool
|
||||
|
||||
for _, folder := range folders {
|
||||
currentPath += folder
|
||||
for _, element := range elements {
|
||||
currentPath += element
|
||||
|
||||
if format := folderFormatMap[currentPath]; len(format) != 0 {
|
||||
folder = fmt.Sprintf(format, folder)
|
||||
element = fmt.Sprintf(format, element)
|
||||
display = true
|
||||
}
|
||||
|
||||
result = append(result, &Folder{Name: folder, Path: currentPath, Display: display})
|
||||
folders = append(folders, &Folder{Name: element, Path: currentPath, Display: display})
|
||||
|
||||
currentPath += pt.pathSeparator
|
||||
|
||||
display = false
|
||||
}
|
||||
|
||||
return result
|
||||
return folders
|
||||
}
|
||||
|
||||
func (pt *Path) makeFolderFormatMap() map[string]string {
|
||||
|
@ -838,7 +839,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 {
|
||||
folderFormatMap[dir.ParentFolder] = gitDirFormat
|
||||
// Make it consistent with the modified path.
|
||||
path := pt.join(pt.replaceMappedLocations(dir.ParentFolder))
|
||||
folderFormatMap[path] = gitDirFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -459,7 +459,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterFull,
|
||||
Expected: "PSDRIVE:/ | src",
|
||||
Expected: "PSDRIVE: | src",
|
||||
HomePath: homeDir,
|
||||
Pwd: "/foo",
|
||||
Pswd: "PSDRIVE:/src",
|
||||
|
@ -489,7 +489,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: ".. | src",
|
||||
Expected: "PSDRIVE: | src",
|
||||
HomePath: homeDir,
|
||||
Pwd: "/foo",
|
||||
Pswd: "PSDRIVE:/src",
|
||||
|
@ -591,7 +591,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: "PSDRIVE:/ | .. | init",
|
||||
Expected: "PSDRIVE: | .. | init",
|
||||
HomePath: homeDir,
|
||||
Pwd: "/foo",
|
||||
Pswd: "PSDRIVE:/src/init",
|
||||
|
@ -630,7 +630,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: ".. > foo",
|
||||
Expected: "~ > foo",
|
||||
HomePath: homeDir,
|
||||
Pwd: homeDir + "/foo",
|
||||
PathSeparator: "/",
|
||||
|
@ -640,7 +640,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: ".. > foo > bar",
|
||||
Expected: "~ > foo > bar",
|
||||
HomePath: homeDir,
|
||||
Pwd: homeDir + "/foo/bar",
|
||||
PathSeparator: "/",
|
||||
|
@ -661,7 +661,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: ".. | space foo",
|
||||
Expected: "~ | space foo",
|
||||
HomePath: homeDir,
|
||||
Pwd: homeDir + "/space foo",
|
||||
PathSeparator: "/",
|
||||
|
@ -752,7 +752,7 @@ func TestAgnosterPathStyles(t *testing.T) {
|
|||
},
|
||||
{
|
||||
Style: AgnosterShort,
|
||||
Expected: ".. > foo",
|
||||
Expected: "~ > foo",
|
||||
HomePath: homeDirWindows,
|
||||
Pwd: homeDirWindows + "\\foo",
|
||||
GOOS: runtime.WINDOWS,
|
||||
|
@ -848,6 +848,7 @@ func TestFullAndFolderPath(t *testing.T) {
|
|||
|
||||
// for Windows paths
|
||||
{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},
|
||||
|
@ -1001,41 +1002,6 @@ func TestFullPathCustomMappedLocations(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPowerlevelMappedLocations(t *testing.T) {
|
||||
cases := []struct {
|
||||
Pwd string
|
||||
MappedLocations map[string]string
|
||||
Expected string
|
||||
}{
|
||||
{Pwd: "/Users/michal/Applications", MappedLocations: map[string]string{"~": "#"}, Expected: "#/Applications"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.Environment)
|
||||
env.On("Home").Return("/Users/michal")
|
||||
env.On("Pwd").Return(tc.Pwd)
|
||||
env.On("GOOS").Return(runtime.DARWIN)
|
||||
env.On("PathSeparator").Return("/")
|
||||
env.On("Shell").Return(shell.GENERIC)
|
||||
env.On("DebugF", testify_.Anything, testify_.Anything).Return(nil)
|
||||
env.On("Flags").Return(&runtime.Flags{})
|
||||
|
||||
path := &Path{
|
||||
env: env,
|
||||
props: properties.Map{
|
||||
properties.Style: Powerlevel,
|
||||
MappedLocations: tc.MappedLocations,
|
||||
},
|
||||
}
|
||||
|
||||
path.setPaths()
|
||||
path.setStyle()
|
||||
|
||||
got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path)
|
||||
assert.Equal(t, tc.Expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFolderPathCustomMappedLocations(t *testing.T) {
|
||||
pwd := abcd
|
||||
env := new(mock.Environment)
|
||||
|
@ -1501,46 +1467,155 @@ func TestGetFolderSeparator(t *testing.T) {
|
|||
|
||||
func TestNormalizePath(t *testing.T) {
|
||||
cases := []struct {
|
||||
Input string
|
||||
HomeDir string
|
||||
GOOS string
|
||||
Expected string
|
||||
Case string
|
||||
Input string
|
||||
HomeDir string
|
||||
GOOS string
|
||||
PathSeparator string
|
||||
Expected string
|
||||
}{
|
||||
{Input: "/foo/~/bar", HomeDir: homeDirWindows, GOOS: runtime.WINDOWS, Expected: "\\foo\\~\\bar"},
|
||||
{Input: homeDirWindows + "\\Foo", HomeDir: homeDirWindows, GOOS: runtime.WINDOWS, Expected: "c:\\users\\someone\\foo"},
|
||||
{Input: "~/Bob\\Foo", HomeDir: homeDir, GOOS: runtime.LINUX, Expected: homeDir + "/Bob\\Foo"},
|
||||
{Input: "~/Bob\\Foo", HomeDir: homeDir, GOOS: runtime.DARWIN, Expected: homeDir + "/bob\\foo"},
|
||||
{Input: "~\\Bob\\Foo", HomeDir: homeDirWindows, GOOS: runtime.WINDOWS, Expected: "c:\\users\\someone\\bob\\foo"},
|
||||
{Input: "/foo/~/bar", HomeDir: homeDir, GOOS: runtime.LINUX, Expected: "/foo/~/bar"},
|
||||
{Input: "~/baz", HomeDir: homeDir, GOOS: runtime.LINUX, Expected: homeDir + "/baz"},
|
||||
{Input: "~/baz", HomeDir: homeDirWindows, GOOS: runtime.WINDOWS, Expected: "c:\\users\\someone\\baz"},
|
||||
{
|
||||
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: "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: "Windows: home prefix",
|
||||
Input: "~\\Bob\\Foo",
|
||||
HomeDir: homeDirWindows,
|
||||
GOOS: runtime.WINDOWS,
|
||||
PathSeparator: `\`,
|
||||
Expected: "c:\\users\\someone\\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",
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.Environment)
|
||||
env.On("Home").Return(tc.HomeDir)
|
||||
env.On("GOOS").Return(tc.GOOS)
|
||||
pt := &Path{
|
||||
env: env,
|
||||
|
||||
if len(tc.PathSeparator) == 0 {
|
||||
tc.PathSeparator = "/"
|
||||
}
|
||||
|
||||
env.On("PathSeparator").Return(tc.PathSeparator)
|
||||
pt := &Path{env: env}
|
||||
got := pt.normalize(tc.Input)
|
||||
assert.Equal(t, tc.Expected, got)
|
||||
assert.Equal(t, tc.Expected, got, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceMappedLocations(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Pwd string
|
||||
Expected string
|
||||
Case string
|
||||
Pwd string
|
||||
MappedLocationsEnabled bool
|
||||
Expected string
|
||||
}{
|
||||
{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/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 {
|
||||
|
@ -1556,11 +1631,12 @@ func TestReplaceMappedLocations(t *testing.T) {
|
|||
path := &Path{
|
||||
env: env,
|
||||
props: properties.Map{
|
||||
MappedLocationsEnabled: false,
|
||||
MappedLocationsEnabled: tc.MappedLocationsEnabled,
|
||||
MappedLocations: map[string]string{
|
||||
abcd: "#",
|
||||
"/f/g/h/*": "^",
|
||||
"/c/l/k/*": "",
|
||||
"~": "@",
|
||||
"~/j/*": "",
|
||||
},
|
||||
},
|
||||
|
@ -1600,8 +1676,8 @@ func TestSplitPath(t *testing.T) {
|
|||
GitDir: &runtime.FileInfo{IsDir: true, ParentFolder: "/a/b/c"},
|
||||
GitDirFormat: "<b>%s</b>",
|
||||
Expected: Folders{
|
||||
{Name: "<b>c</b>", Path: "/a/b/c", Display: true},
|
||||
{Name: "d", Path: "/a/b/c/d"},
|
||||
{Name: "<b>c</b>", Path: "~/c", Display: true},
|
||||
{Name: "d", Path: "~/c/d"},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1622,6 +1698,7 @@ func TestSplitPath(t *testing.T) {
|
|||
|
||||
for _, tc := range cases {
|
||||
env := new(mock.Environment)
|
||||
env.On("PathSeparator").Return("/")
|
||||
env.On("Home").Return("/a/b")
|
||||
env.On("HasParentFilePath", ".git", false).Return(tc.GitDir, nil)
|
||||
env.On("GOOS").Return(tc.GOOS)
|
||||
|
|
|
@ -86,6 +86,8 @@ const (
|
|||
hyperLinkText = "<TEXT>"
|
||||
hyperLinkTextEnd = "</TEXT>"
|
||||
|
||||
empty = "<>"
|
||||
|
||||
startProgress = "\x1b]9;4;3;0\x07"
|
||||
endProgress = "\x1b]9;4;0;0\x07"
|
||||
|
||||
|
@ -160,10 +162,6 @@ func Pwd(pwdType, userName, hostName, pwd string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
if strings.HasSuffix(pwd, ":") {
|
||||
pwd += `/`
|
||||
}
|
||||
|
||||
switch pwdType {
|
||||
case OSC7:
|
||||
return fmt.Sprintf(formats.Osc7, hostName, pwd)
|
||||
|
@ -346,6 +344,9 @@ func Write(background, foreground color.Ansi, text string) {
|
|||
i += len([]rune(match[ANCHOR])) - 1
|
||||
builder.WriteString(formats.HyperlinkEnd)
|
||||
continue
|
||||
case empty:
|
||||
i += len([]rune(match[ANCHOR])) - 1
|
||||
continue
|
||||
}
|
||||
|
||||
i = writeArchorOverride(match, background, i)
|
||||
|
|
|
@ -77,7 +77,9 @@ For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do t
|
|||
- To make mapped locations work cross-platform, use `/` as the path separator, Oh My Posh will
|
||||
automatically match effective separators based on the running operating system.
|
||||
- If you want to match all child directories, you can use `*` as a wildcard, for example:
|
||||
`"C:/Users/Bill/*": "$"` will turn `C:/Users/Bill/Downloads` into `$/Downloads`.
|
||||
`"C:/Users/Bill/*": "$"` will turn `C:/Users/Bill/Downloads` into `$/Downloads` but leave `C:/Users/Bill` unchanged.
|
||||
- To prevent mangling path elements, if you use any text style tags (e.g., `<lightGreen>...</>`) in replacement values,
|
||||
you should avoid using a chevron character (`<`/`>`) in the `folder_separator_icon` property, and vice versa.
|
||||
- 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. This means that for
|
||||
user Bill, who has a user account `Bill` on Windows and `bill` on Linux, `~/Foo` might match
|
||||
|
|
Loading…
Reference in a new issue