feat(path): add colored full path

This commit is contained in:
Jan De Dobbeleer 2023-04-20 07:00:09 +02:00 committed by Jan De Dobbeleer
parent 4e9e753e13
commit facc24d290
3 changed files with 148 additions and 107 deletions

View file

@ -73,6 +73,8 @@ const (
MaxWidth properties.Property = "max_width" MaxWidth properties.Property = "max_width"
// Hides the root location if it doesn't fit in max_depth. Used in Agnoster Short // Hides the root location if it doesn't fit in max_depth. Used in Agnoster Short
HideRootLocation properties.Property = "hide_root_location" HideRootLocation properties.Property = "hide_root_location"
// A foreground color cycle
Cycle properties.Property = "cycle"
) )
func (pt *Path) Template() string { func (pt *Path) Template() string {
@ -233,41 +235,39 @@ func (pt *Path) pathDepth(pwd string) int {
} }
func (pt *Path) getAgnosterPath() string { func (pt *Path) getAgnosterPath() string {
var buffer strings.Builder
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() splitted := strings.Split(pt.relative, pt.env.PathSeparator())
elements := strings.Split(pt.relative, pt.env.PathSeparator()) if pt.root == pt.env.PathSeparator() {
if pt.root != pt.env.PathSeparator() { pt.root = splitted[0]
elements = append([]string{pt.root}, elements...) splitted = splitted[1:]
} }
n := len(elements)
buffer.WriteString(elements[0]) var elements []string
for i := 2; i < n; i++ { n := len(splitted)
buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon)) for i := 1; i < n; i++ {
elements = append(elements, folderIcon)
} }
if n > 1 { elements = append(elements, splitted[n-1])
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1]))
} return pt.colorizePath(pt.root, elements)
return buffer.String()
} }
func (pt *Path) getAgnosterLeftPath() string { func (pt *Path) getAgnosterLeftPath() string {
var buffer strings.Builder
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() splitted := strings.Split(pt.relative, pt.env.PathSeparator())
elements := strings.Split(pt.relative, pt.env.PathSeparator()) if pt.root == pt.env.PathSeparator() {
if pt.root != pt.env.PathSeparator() { pt.root = splitted[0]
elements = append([]string{pt.root}, elements...) splitted = splitted[1:]
} }
n := len(elements)
buffer.WriteString(elements[0]) var elements []string
if n > 1 { n := len(splitted)
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[1])) elements = append(elements, splitted[0])
for i := 1; i < n; i++ {
elements = append(elements, folderIcon)
} }
for i := 2; i < n; i++ {
buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon)) return pt.colorizePath(pt.root, elements)
}
return buffer.String()
} }
func (pt *Path) getRelevantLetter(folder string) string { func (pt *Path) getRelevantLetter(folder string) string {
@ -284,43 +284,47 @@ func (pt *Path) getRelevantLetter(folder string) string {
} }
func (pt *Path) getLetterPath() string { func (pt *Path) getLetterPath() string {
var buffer strings.Builder splitted := strings.Split(pt.relative, pt.env.PathSeparator())
separator := pt.getFolderSeparator() if pt.root == pt.env.PathSeparator() {
elements := strings.Split(pt.relative, pt.env.PathSeparator()) pt.root = splitted[0]
if pt.root != pt.env.PathSeparator() { splitted = splitted[1:]
elements = append([]string{pt.root}, elements...)
} }
n := len(elements) pt.root = pt.getRelevantLetter(pt.root)
var elements []string
n := len(splitted)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
letter := pt.getRelevantLetter(elements[i]) letter := pt.getRelevantLetter(splitted[i])
if i != 0 { elements = append(elements, letter)
buffer.WriteString(separator)
}
buffer.WriteString(letter)
} }
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1])) elements = append(elements, splitted[n-1])
return buffer.String()
return pt.colorizePath(pt.root, elements)
} }
func (pt *Path) getUniqueLettersPath(maxWidth int) string { func (pt *Path) getUniqueLettersPath(maxWidth int) string {
var buffer strings.Builder
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(pt.relative, pt.env.PathSeparator()) splitted := 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.root = splitted[0]
splitted = splitted[1:]
} }
if maxWidth > 0 { if maxWidth > 0 {
path := strings.Join(elements, separator) path := strings.Join(splitted, separator)
if len(path) <= maxWidth { if len(path) <= maxWidth {
return path return pt.colorizePath(pt.root, splitted)
} }
} }
n := len(elements) pt.root = pt.getRelevantLetter(pt.root)
var elements []string
n := len(splitted)
letters := make(map[string]bool) letters := make(map[string]bool)
letters[pt.root] = true
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
folder := elements[i] folder := splitted[i]
letter := pt.getRelevantLetter(folder) letter := pt.getRelevantLetter(folder)
for letters[letter] { for letters[letter] {
if letter == folder { if letter == folder {
@ -329,32 +333,33 @@ func (pt *Path) getUniqueLettersPath(maxWidth int) string {
letter += folder[len(letter) : len(letter)+1] letter += folder[len(letter) : len(letter)+1]
} }
letters[letter] = true letters[letter] = true
if i != 0 { elements = append(elements, letter)
buffer.WriteString(separator)
}
buffer.WriteString(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 {
trailing := strings.Join(elements[i+1:], separator) list := splitted[i+1:]
leftover := maxWidth - buffer.Len() - len(trailing) - len(separator) list = append(list, elements...)
current := strings.Join(list, separator)
leftover := maxWidth - len(current) - len(pt.root) - len(separator)
if leftover >= 0 { if leftover >= 0 {
buffer.WriteString(fmt.Sprintf("%s%s", separator, trailing)) elements = append(elements, strings.Join(splitted[i+1:], separator))
return buffer.String() return pt.colorizePath(pt.root, elements)
} }
} }
} }
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1])) elements = append(elements, splitted[n-1])
return buffer.String()
return pt.colorizePath(pt.root, elements)
} }
func (pt *Path) getAgnosterFullPath() string { func (pt *Path) getAgnosterFullPath() string {
path := strings.Trim(pt.relative, pt.env.PathSeparator()) splitted := strings.Split(pt.relative, pt.env.PathSeparator())
path = pt.replaceFolderSeparators(path)
if pt.root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
return path pt.root = splitted[0]
splitted = splitted[1:]
} }
return pt.root + pt.getFolderSeparator() + path
return pt.colorizePath(pt.root, splitted)
} }
func (pt *Path) getAgnosterShortPath() string { func (pt *Path) getAgnosterShortPath() string {
@ -372,11 +377,11 @@ func (pt *Path) getAgnosterShortPath() string {
return pt.getAgnosterFullPath() return pt.getAgnosterFullPath()
} }
pathSeparator := pt.env.PathSeparator() pathSeparator := pt.env.PathSeparator()
folderSeparator := pt.getFolderSeparator()
rel := strings.TrimPrefix(pt.relative, pathSeparator) rel := strings.TrimPrefix(pt.relative, pathSeparator)
splitted := strings.Split(rel, pathSeparator) splitted := strings.Split(rel, pathSeparator)
splitPos := pathDepth - maxDepth splitPos := pathDepth - maxDepth
var buffer strings.Builder // var buffer strings.Builder
var elements []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
@ -384,29 +389,24 @@ func (pt *Path) getAgnosterShortPath() string {
root = splitted[0] root = splitted[0]
room-- room--
} }
if hideRootLocation || room > 0 {
elements = append(elements, folderIcon)
}
if hideRootLocation { if hideRootLocation {
buffer.WriteString(folderIcon) root = ""
} else {
buffer.WriteString(root)
if room > 0 {
buffer.WriteString(folderSeparator)
buffer.WriteString(folderIcon)
}
} }
for i := splitPos; i < pathDepth; i++ { for i := splitPos; i < pathDepth; i++ {
buffer.WriteString(fmt.Sprintf("%s%s", folderSeparator, splitted[i])) elements = append(elements, splitted[i])
} }
return buffer.String() return pt.colorizePath(root, elements)
} }
func (pt *Path) getFullPath() string { func (pt *Path) getFullPath() string {
rel := pt.relative elements := strings.Split(pt.relative, pt.env.PathSeparator())
pathSeparator := pt.env.PathSeparator() return pt.colorizePath(pt.root, elements)
if len(pt.root) != 0 && pt.root != pathSeparator && !strings.HasSuffix(pt.root, pathSeparator) {
rel = pathSeparator + rel
}
path := pt.replaceFolderSeparators(rel)
return pt.root + path
} }
func (pt *Path) getFolderPath() string { func (pt *Path) getFolderPath() string {
@ -573,3 +573,31 @@ func (pt *Path) replaceFolderSeparators(pwd string) string {
pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator) pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator)
return pwd return pwd
} }
func (pt *Path) colorizePath(root string, elements []string) string {
cycle := pt.props.GetStringArray(Cycle, []string{})
folderSeparator := pt.getFolderSeparator()
if len(cycle) != 0 {
colorize := "<%s>%s</>"
if len(root) != 0 {
root = fmt.Sprintf(colorize, cycle[0], root)
cycle = append(cycle[1:], cycle[0])
}
for i, element := range elements {
if len(element) == 0 {
continue
}
colored := fmt.Sprintf(colorize, cycle[0], element)
elements[i] = colored
cycle = append(cycle[1:], cycle[0])
}
}
if root == pt.env.PathSeparator() || len(root) == 0 {
return root + strings.Join(elements, folderSeparator)
}
return root + folderSeparator + strings.Join(elements, folderSeparator)
}

View file

@ -956,6 +956,7 @@ func TestAgnosterPath(t *testing.T) {
PWD string PWD string
GOOS string GOOS string
PathSeparator string PathSeparator string
Cycle []string
}{ }{
{ {
Case: "Windows registry drive case sensitive", Case: "Windows registry drive case sensitive",
@ -1073,9 +1074,17 @@ func TestAgnosterPath(t *testing.T) {
PWD: "/mnt/folder/location", PWD: "/mnt/folder/location",
PathSeparator: "/", PathSeparator: "/",
}, },
{
Case: "Unix, colorize",
Expected: "<blue>mnt</> > <yellow>f</> > <blue>location</>",
Home: homeDir,
PWD: "/mnt/folder/location",
PathSeparator: "/",
Cycle: []string{"blue", "yellow"},
},
} }
for _, tc := range cases { //nolint:dupl for _, tc := range cases {
env := new(mock.MockedEnvironment) env := new(mock.MockedEnvironment)
env.On("Home").Return(tc.Home) env.On("Home").Return(tc.Home)
env.On("PathSeparator").Return(tc.PathSeparator) env.On("PathSeparator").Return(tc.PathSeparator)
@ -1093,6 +1102,7 @@ func TestAgnosterPath(t *testing.T) {
FolderSeparatorIcon: " > ", FolderSeparatorIcon: " > ",
FolderIcon: "f", FolderIcon: "f",
HomeIcon: "~", HomeIcon: "~",
Cycle: tc.Cycle,
}, },
} }
path.setPaths() path.setPaths()
@ -1229,7 +1239,7 @@ func TestAgnosterLeftPath(t *testing.T) {
}, },
} }
for _, tc := range cases { //nolint:dupl for _, tc := range cases {
env := new(mock.MockedEnvironment) env := new(mock.MockedEnvironment)
env.On("Home").Return(tc.Home) env.On("Home").Return(tc.Home)
env.On("PathSeparator").Return(tc.PathSeparator) env.On("PathSeparator").Return(tc.PathSeparator)

View file

@ -10,36 +10,39 @@ Display the current path.
## Sample Configuration ## Sample Configuration
import Config from '@site/src/components/Config.js'; import Config from "@site/src/components/Config.js";
<Config data={{ <Config
"type": "path", data={{
"style": "powerline", type: "path",
"powerline_symbol": "\uE0B0", style: "powerline",
"foreground": "#ffffff", powerline_symbol: "\uE0B0",
"background": "#61AFEF", foreground: "#ffffff",
"properties": { background: "#61AFEF",
"style": "folder", properties: {
"mapped_locations": { style: "folder",
"C:\\temp": "\ue799" mapped_locations: {
} "C:\\temp": "\ue799",
} },
}}/> },
}}
/>
## Properties ## Properties
| Name | Type | Description | | Name | Type | Description |
| --------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | | --------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `folder_separator_icon` | `string` | the symbol to use as a separator between folders - defaults to platform path separator | | `folder_separator_icon` | `string` | the symbol to use as a separator between folders - defaults to platform path separator |
| `folder_separator_template` | `string` | the [template][templates] to use as a separator between folders - defaults to `` | | `folder_separator_template` | `string` | the [template][templates] to use as a separator between folders - defaults to `` |
| `home_icon` | `string` | the icon to display when at `$HOME`, defaults to `~` | | `home_icon` | `string` | the icon to display when at `$HOME`, defaults to `~` |
| `folder_icon` | `string` | the icon to use as a folder indication - defaults to `..` | | `folder_icon` | `string` | the icon to use as a folder indication - defaults to `..` |
| `windows_registry_icon` | `string` | the icon to display when in the Windows registry - defaults to `\uE0B1` | | `windows_registry_icon` | `string` | the icon to display when in the Windows registry - defaults to `\uE0B1` |
| `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 foreground colors to cycle through to colorize the individual path folders |
## Mapped Locations ## Mapped Locations