feat(path): highlight git directory

resolves #4727
This commit is contained in:
Jan De Dobbeleer 2024-03-24 16:38:52 +01:00 committed by Jan De Dobbeleer
parent c072bb941d
commit 6e1137512c
3 changed files with 227 additions and 85 deletions

View file

@ -25,6 +25,7 @@ type Path struct {
Location string Location string
Writable bool Writable bool
RootDir bool RootDir bool
Folders Folders
} }
const ( const (
@ -48,8 +49,8 @@ const (
Short string = "short" Short string = "short"
// Full displays the full path // Full displays the full path
Full string = "full" Full string = "full"
// Folder displays the current folder // FolderType displays the current folder
Folder string = "folder" FolderType string = "folder"
// Mixed like agnoster, but if a folder name is short enough, it is displayed as-is // Mixed like agnoster, but if a folder name is short enough, it is displayed as-is
Mixed string = "mixed" Mixed string = "mixed"
// Letter like agnoster, but with the first letter of each folder name // Letter like agnoster, but with the first letter of each folder name
@ -81,6 +82,8 @@ const (
FolderFormat properties.Property = "folder_format" FolderFormat properties.Property = "folder_format"
// format to use on the first and last folder of the path // format to use on the first and last folder of the path
EdgeFormat properties.Property = "edge_format" EdgeFormat properties.Property = "edge_format"
// GitDirFormat format to use on the git directory
GitDirFormat properties.Property = "gitdir_format"
) )
func (pt *Path) Template() string { func (pt *Path) Template() string {
@ -105,25 +108,34 @@ func (pt *Path) Enabled() bool {
} }
func (pt *Path) setPaths() { func (pt *Path) setPaths() {
defer func() {
pt.Folders = pt.splitPath()
}()
pt.pwd = pt.env.Pwd() pt.pwd = pt.env.Pwd()
if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 { if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 {
pt.pwd = pt.env.Flags().PSWD pt.pwd = pt.env.Flags().PSWD
} }
if len(pt.pwd) == 0 { if len(pt.pwd) == 0 {
return return
} }
// ensure a clean path // ensure a clean path
pt.root, pt.relative = pt.replaceMappedLocations() pt.root, pt.relative = pt.replaceMappedLocations()
// this is a full replacement of the parent // this is a full replacement of the parent
if len(pt.root) == 0 { if len(pt.root) == 0 {
pt.pwd = pt.relative pt.pwd = pt.relative
return return
} }
pathSeparator := pt.env.PathSeparator() pathSeparator := pt.env.PathSeparator()
if !strings.HasSuffix(pt.root, pathSeparator) && len(pt.relative) > 0 { if !strings.HasSuffix(pt.root, pathSeparator) && len(pt.relative) > 0 {
pt.pwd = pt.root + pathSeparator + pt.relative pt.pwd = pt.root + pathSeparator + pt.relative
return return
} }
pt.pwd = pt.root + pt.relative pt.pwd = pt.root + pt.relative
} }
@ -173,7 +185,7 @@ func (pt *Path) setStyle() {
fallthrough fallthrough
case Full: case Full:
pt.Path = pt.getFullPath() pt.Path = pt.getFullPath()
case Folder: case FolderType:
pt.Path = pt.getFolderPath() pt.Path = pt.getFolderPath()
case Powerlevel: case Powerlevel:
maxWidth := int(pt.props.GetFloat64(MaxWidth, 0)) maxWidth := int(pt.props.GetFloat64(MaxWidth, 0))
@ -213,15 +225,17 @@ func (pt *Path) getMixedPath() string {
threshold := int(pt.props.GetFloat64(MixedThreshold, 4)) threshold := int(pt.props.GetFloat64(MixedThreshold, 4))
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{pt.root}, elements...) pt.Folders = append(Folders{{Name: pt.root}}, pt.Folders...)
} }
n := len(elements)
buffer.WriteString(elements[0]) n := len(pt.Folders)
buffer.WriteString(pt.Folders[0].Name)
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
folder := elements[i] folder := pt.Folders[i].Name
if len(folder) > threshold && i != n-1 { if len(folder) > threshold && i != n-1 && !pt.Folders[i].Display {
folder = folderIcon folder = folderIcon
} }
buffer.WriteString(fmt.Sprintf("%s%s", separator, folder)) buffer.WriteString(fmt.Sprintf("%s%s", separator, folder))
@ -229,33 +243,27 @@ func (pt *Path) getMixedPath() string {
return buffer.String() return buffer.String()
} }
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
}
func (pt *Path) getAgnosterPath() string { func (pt *Path) getAgnosterPath() string {
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
splitted := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
pt.root = splitted[0] pt.root = pt.Folders[0].Name
splitted = splitted[1:] pt.Folders = pt.Folders[1:]
} }
var elements []string var elements []string
n := len(splitted) n := len(pt.Folders)
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
if pt.Folders[i].Display {
elements = append(elements, pt.Folders[i].Name)
continue
}
elements = append(elements, folderIcon) elements = append(elements, folderIcon)
} }
if len(splitted) > 0 { if len(pt.Folders) > 0 {
elements = append(elements, splitted[n-1]) elements = append(elements, pt.Folders[n-1].Name)
} }
return pt.colorizePath(pt.root, elements) return pt.colorizePath(pt.root, elements)
@ -263,52 +271,66 @@ func (pt *Path) getAgnosterPath() string {
func (pt *Path) getAgnosterLeftPath() string { func (pt *Path) getAgnosterLeftPath() string {
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
splitted := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
pt.root = splitted[0] pt.root = pt.Folders[0].Name
splitted = splitted[1:] pt.Folders = pt.Folders[1:]
} }
var elements []string var elements []string
n := len(splitted) n := len(pt.Folders)
elements = append(elements, splitted[0]) elements = append(elements, pt.Folders[0].Name)
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
if pt.Folders[i].Display {
elements = append(elements, pt.Folders[i].Name)
continue
}
elements = append(elements, folderIcon) elements = append(elements, folderIcon)
} }
return pt.colorizePath(pt.root, elements) return pt.colorizePath(pt.root, elements)
} }
func (pt *Path) getRelevantLetter(folder string) string { func (pt *Path) getRelevantLetter(folder *Folder) string {
if folder.Display {
return folder.Name
}
// check if there is at least a letter we can use // check if there is at least a letter we can use
matches := regex.FindNamedRegexMatch(`(?P<letter>[\p{L}0-9]).*`, folder) matches := regex.FindNamedRegexMatch(`(?P<letter>[\p{L}0-9]).*`, folder.Name)
if matches == nil || len(matches["letter"]) == 0 { if matches == nil || len(matches["letter"]) == 0 {
// no letter found, keep the folder unchanged // no letter found, keep the folder unchanged
return folder return folder.Name
} }
letter := matches["letter"] letter := matches["letter"]
// handle non-letter characters before the first found letter // handle non-letter characters before the first found letter
letter = folder[0:strings.Index(folder, letter)] + letter letter = folder.Name[0:strings.Index(folder.Name, letter)] + letter
return letter return letter
} }
func (pt *Path) getLetterPath() string { func (pt *Path) getLetterPath() string {
splitted := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
pt.root = splitted[0] pt.root = pt.Folders[0].Name
splitted = splitted[1:] pt.Folders = pt.Folders[1:]
} }
pt.root = pt.getRelevantLetter(pt.root)
pt.root = pt.getRelevantLetter(&Folder{Name: pt.root})
var elements []string var elements []string
n := len(splitted) n := len(pt.Folders)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
letter := pt.getRelevantLetter(splitted[i]) if pt.Folders[i].Display {
elements = append(elements, pt.Folders[i].Name)
continue
}
letter := pt.getRelevantLetter(pt.Folders[i])
elements = append(elements, letter) elements = append(elements, letter)
} }
if len(splitted) > 0 { if len(pt.Folders) > 0 {
elements = append(elements, splitted[n-1]) elements = append(elements, pt.Folders[n-1].Name)
} }
return pt.colorizePath(pt.root, elements) return pt.colorizePath(pt.root, elements)
@ -316,98 +338,101 @@ func (pt *Path) getLetterPath() string {
func (pt *Path) getUniqueLettersPath(maxWidth int) string { func (pt *Path) getUniqueLettersPath(maxWidth int) string {
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
splitted := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
pt.root = splitted[0] pt.root = pt.Folders[0].Name
splitted = splitted[1:] pt.Folders = pt.Folders[1:]
} }
if maxWidth > 0 { if maxWidth > 0 {
path := strings.Join(splitted, separator) path := strings.Join(pt.Folders.List(), separator)
if len(path) <= maxWidth { if len(path) <= maxWidth {
return pt.colorizePath(pt.root, splitted) return pt.colorizePath(pt.root, pt.Folders.List())
} }
} }
pt.root = pt.getRelevantLetter(pt.root) pt.root = pt.getRelevantLetter(&Folder{Name: pt.root})
var elements []string var elements []string
n := len(splitted) n := len(pt.Folders)
letters := make(map[string]bool) letters := make(map[string]bool)
letters[pt.root] = true letters[pt.root] = true
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
folder := splitted[i] folder := pt.Folders[i].Name
letter := pt.getRelevantLetter(folder) letter := pt.getRelevantLetter(pt.Folders[i])
for letters[letter] { for letters[letter] {
if letter == folder { if letter == folder {
break break
} }
letter += folder[len(letter) : len(letter)+1] letter += folder[len(letter) : len(letter)+1]
} }
letters[letter] = true letters[letter] = true
elements = append(elements, letter) elements = append(elements, letter)
// only return early on maxWidth > 0 // only return early on maxWidth > 0
// this enables the powerlevel10k behavior // this enables the powerlevel10k behavior
if maxWidth > 0 { if maxWidth > 0 {
list := splitted[i+1:] list := pt.Folders[i+1:].List()
list = append(list, elements...) list = append(list, elements...)
current := strings.Join(list, separator) current := strings.Join(list, separator)
leftover := maxWidth - len(current) - len(pt.root) - len(separator) leftover := maxWidth - len(current) - len(pt.root) - len(separator)
if leftover >= 0 { if leftover >= 0 {
elements = append(elements, strings.Join(splitted[i+1:], separator)) elements = append(elements, strings.Join(pt.Folders[i+1:].List(), separator))
return pt.colorizePath(pt.root, elements) return pt.colorizePath(pt.root, elements)
} }
} }
} }
if len(splitted) > 0 { if len(pt.Folders) > 0 {
elements = append(elements, splitted[n-1]) elements = append(elements, pt.Folders[n-1].Name)
} }
return pt.colorizePath(pt.root, elements) return pt.colorizePath(pt.root, elements)
} }
func (pt *Path) getAgnosterFullPath() string { func (pt *Path) getAgnosterFullPath() string {
splitted := strings.Split(pt.relative, pt.env.PathSeparator())
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
pt.root = splitted[0] pt.root = pt.Folders[0].Name
splitted = splitted[1:] pt.Folders = pt.Folders[1:]
} }
return pt.colorizePath(pt.root, splitted) return pt.colorizePath(pt.root, pt.Folders.List())
} }
func (pt *Path) getAgnosterShortPath() string { func (pt *Path) getAgnosterShortPath() string {
pathDepth := pt.pathDepth(pt.relative) pathDepth := len(pt.Folders)
maxDepth := pt.props.GetInt(MaxDepth, 1) maxDepth := pt.props.GetInt(MaxDepth, 1)
if maxDepth < 1 { if maxDepth < 1 {
maxDepth = 1 maxDepth = 1
} }
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
hideRootLocation := pt.props.GetBool(HideRootLocation, false) hideRootLocation := pt.props.GetBool(HideRootLocation, false)
if pathDepth <= maxDepth { if pathDepth <= maxDepth {
if hideRootLocation { if hideRootLocation {
pt.root = folderIcon pt.root = folderIcon
} }
return pt.getAgnosterFullPath() return pt.getAgnosterFullPath()
} }
pathSeparator := pt.env.PathSeparator() pathSeparator := pt.env.PathSeparator()
rel := strings.TrimPrefix(pt.relative, pathSeparator)
splitted := strings.Split(rel, pathSeparator)
splitPos := pathDepth - maxDepth splitPos := pathDepth - maxDepth
// var buffer strings.Builder
var elements []string var folders []string
// unix root, needs to be replaced with the folder we're in at root level // unix root, needs to be replaced with the folder we're in at root level
root := pt.root root := pt.root
room := pathDepth - maxDepth room := pathDepth - maxDepth
if root == pathSeparator { if root == pathSeparator {
root = splitted[0] root = pt.Folders[0].Name
room-- room--
} }
if hideRootLocation || room > 0 { if hideRootLocation || room > 0 {
elements = append(elements, folderIcon) folders = append(folders, folderIcon)
} }
if hideRootLocation { if hideRootLocation {
@ -415,14 +440,14 @@ func (pt *Path) getAgnosterShortPath() string {
} }
for i := splitPos; i < pathDepth; i++ { for i := splitPos; i < pathDepth; i++ {
elements = append(elements, splitted[i]) folders = append(folders, pt.Folders[i].Name)
} }
return pt.colorizePath(root, elements)
return pt.colorizePath(root, folders)
} }
func (pt *Path) getFullPath() string { func (pt *Path) getFullPath() string {
elements := strings.Split(pt.relative, pt.env.PathSeparator()) return pt.colorizePath(pt.root, pt.Folders.List())
return pt.colorizePath(pt.root, elements)
} }
func (pt *Path) getFolderPath() string { func (pt *Path) getFolderPath() string {
@ -649,3 +674,69 @@ func (pt *Path) colorizePath(root string, elements []string) string {
return builder.String() 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
}
func (pt *Path) splitPath() Folders {
result := Folders{}
folders := []string{}
if len(pt.relative) != 0 {
folders = strings.Split(pt.relative, pt.env.PathSeparator())
}
folderFormatMap := pt.makeFolderFormatMap()
currentPath := pt.root
if currentPath == "~" {
currentPath = pt.env.Home() + pt.env.PathSeparator()
}
var display bool
for _, folder := range folders {
currentPath += folder
if format := folderFormatMap[currentPath]; len(format) != 0 {
folder = fmt.Sprintf(format, folder)
display = true
}
result = append(result, &Folder{Name: folder, Path: currentPath, Display: display})
currentPath += pt.env.PathSeparator()
display = false
}
return result
}
func (pt *Path) makeFolderFormatMap() map[string]string {
folderFormatMap := make(map[string]string)
if gitDirFormat := pt.props.GetString(GitDirFormat, ""); len(gitDirFormat) != 0 {
dir, err := pt.env.HasParentFilePath(".git")
if err == nil && dir.IsDir {
folderFormatMap[dir.ParentFolder] = gitDirFormat
}
}
return folderFormatMap
}

View file

@ -787,21 +787,21 @@ func TestFullAndFolderPath(t *testing.T) {
{Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "~|abc"}, {Style: Full, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "~|abc"},
{Style: Full, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "/a|b|c|d"}, {Style: Full, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "/a|b|c|d"},
{Style: Folder, Pwd: "/", Expected: "/"}, {Style: FolderType, Pwd: "/", Expected: "/"},
{Style: Folder, Pwd: homeDir, Expected: "~"}, {Style: FolderType, Pwd: homeDir, Expected: "~"},
{Style: Folder, Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, {Style: FolderType, Pwd: homeDir, Expected: "someone", DisableMappedLocations: true},
{Style: Folder, Pwd: homeDir + abc, Expected: "abc"}, {Style: FolderType, Pwd: homeDir + abc, Expected: "abc"},
{Style: Folder, Pwd: abcd, Expected: "d"}, {Style: FolderType, Pwd: abcd, Expected: "d"},
{Style: Folder, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"}, {Style: FolderType, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"},
{Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"}, {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "~"},
{Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "someone", DisableMappedLocations: true}, {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir, Expected: "someone", DisableMappedLocations: true},
{Style: Folder, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "abc"}, {Style: FolderType, FolderSeparatorIcon: "|", Pwd: homeDir + abc, Expected: "abc"},
{Style: Folder, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "d"}, {Style: FolderType, FolderSeparatorIcon: "|", Pwd: abcd, Expected: "d"},
// for Windows paths // for Windows paths
{Style: Folder, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: platform.WINDOWS}, {Style: FolderType, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: platform.WINDOWS},
{Style: Folder, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: platform.WINDOWS}, {Style: FolderType, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: platform.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: platform.WINDOWS}, {Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows, Expected: "~", PathSeparator: "\\", GOOS: platform.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows + "\\abc", Expected: "~\\abc", PathSeparator: "\\", GOOS: platform.WINDOWS}, {Style: Full, FolderSeparatorIcon: "\\", Pwd: homeDirWindows + "\\abc", Expected: "~\\abc", PathSeparator: "\\", GOOS: platform.WINDOWS},
{Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\posh", Expected: "C:\\Users\\posh", PathSeparator: "\\", GOOS: platform.WINDOWS}, {Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\posh", Expected: "C:\\Users\\posh", PathSeparator: "\\", GOOS: platform.WINDOWS},
@ -994,7 +994,7 @@ func TestFolderPathCustomMappedLocations(t *testing.T) {
path := &Path{ path := &Path{
env: env, env: env,
props: properties.Map{ props: properties.Map{
properties.Style: Folder, properties.Style: FolderType,
MappedLocations: map[string]string{ MappedLocations: map[string]string{
abcd: "#", abcd: "#",
}, },
@ -1502,3 +1502,53 @@ func TestReplaceMappedLocations(t *testing.T) {
assert.Equal(t, tc.Expected, path.pwd) assert.Equal(t, tc.Expected, path.pwd)
} }
} }
func TestSplitPath(t *testing.T) {
cases := []struct {
Case string
Relative string
Root string
GitDir *platform.FileInfo
GitDirFormat string
Expected Folders
}{
{Case: "Root directory", Root: "/", Expected: Folders{}},
{
Case: "Regular directory",
Root: "/",
Relative: "c/d",
Expected: Folders{
{Name: "c", Path: "/c"},
{Name: "d", Path: "/c/d"},
},
},
{
Case: "Home directory - git folder",
Root: "~",
Relative: "c/d",
GitDir: &platform.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"},
},
},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("PathSeparator").Return("/")
env.On("Home").Return("/a/b")
env.On("HasParentFilePath", ".git").Return(tc.GitDir, nil)
path := &Path{
env: env,
props: properties.Map{
GitDirFormat: tc.GitDirFormat,
},
root: tc.Root,
relative: tc.Relative,
}
got := path.splitPath()
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

View file

@ -40,12 +40,13 @@ import Config from "@site/src/components/Config.js";
| `style` | `enum` | how to display the current path | | `style` | `enum` | how to display the current path |
| `mixed_threshold` | `number` | the maximum length of a path segment that will be displayed when using `Mixed` - defaults to `4` | | `mixed_threshold` | `number` | the maximum length of a path segment that will be displayed when using `Mixed` - defaults to `4` |
| `max_depth` | `number` | maximum path depth to display before shortening when using `agnoster_short`, defaults to `1` | | `max_depth` | `number` | maximum path depth to display before shortening when using `agnoster_short`, defaults to `1` |
| `max_width` | `number` | maximum path length to display when using `powerlevel`, defaults to `0` | | `max_width` | `number` | maximum path length to display when using `powerlevel` - defaults to `0` |
| `hide_root_location` | `boolean` | hides the root location if it doesn't fit in the last `max_depth` folders, when using `agnoster_short` - defaults to `false` | | `hide_root_location` | `boolean` | hides the root location if it doesn't fit in the last `max_depth` folders, when using `agnoster_short` - defaults to `false` |
| `cycle` | `[]string` | a list of color overrides to cycle through to colorize the individual path folders, e.g. `[ "#ffffff,#111111" ]` | | `cycle` | `[]string` | a list of color overrides to cycle through to colorize the individual path folders, e.g. `[ "#ffffff,#111111" ]` |
| `cycle_folder_separator` | `boolean` | colorize the `folder_separator_icon` as well when using a cycle - defaults to `false` | | `cycle_folder_separator` | `boolean` | colorize the `folder_separator_icon` as well when using a cycle - defaults to `false` |
| `folder_format` | `string` | format to use on individual path folders - defaults to `%s` | | `folder_format` | `string` | format to use on individual path folders - defaults to `%s` |
| `edge_format` | `string` | format to use on the first and last folder of the path - defaults to `%s` | | `edge_format` | `string` | format to use on the first and last folder of the path - defaults to `%s` |
| `gitdir_format` | `string` | format to use for a git root directory - defaults to `` |
## Mapped Locations ## Mapped Locations