feat(path): allow displaying the cygwin style path

resolves #5074
This commit is contained in:
Jan De Dobbeleer 2024-06-20 12:12:06 +02:00 committed by Jan De Dobbeleer
parent 88246f9bc4
commit b6f5999e21
4 changed files with 166 additions and 43 deletions

View file

@ -17,9 +17,12 @@ type Path struct {
props properties.Properties
env platform.Environment
root string
relative string
pwd string
root string
relative string
pwd string
cygPath bool
windowsPath bool
pathSeparator string
Path string
StackCount int
@ -89,6 +92,8 @@ const (
RightFormat properties.Property = "right_format"
// GitDirFormat format to use on the git directory
GitDirFormat properties.Property = "gitdir_format"
// DisplayCygpath transforms the path to a cygpath format
DisplayCygpath properties.Property = "display_cygpath"
)
func (pt *Path) Template() string {
@ -115,6 +120,20 @@ func (pt *Path) setPaths() {
pt.Folders = pt.splitPath()
}()
displayCygpath := func() bool {
enableCygpath := pt.props.GetBool(DisplayCygpath, false)
if !enableCygpath {
return false
}
isCygwin := pt.env.GOOS() == platform.WINDOWS && pt.env.Shell() == shell.BASH
return isCygwin
}
pt.cygPath = displayCygpath()
pt.windowsPath = pt.env.GOOS() == platform.WINDOWS && !pt.cygPath
pt.pathSeparator = pt.env.PathSeparator()
pt.pwd = pt.env.Pwd()
if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 {
pt.pwd = pt.env.Flags().PSWD
@ -133,9 +152,8 @@ func (pt *Path) setPaths() {
return
}
pathSeparator := pt.env.PathSeparator()
if !strings.HasSuffix(pt.root, pathSeparator) && len(pt.relative) > 0 {
pt.pwd = pt.root + pathSeparator + pt.relative
if !strings.HasSuffix(pt.root, pt.pathSeparator) && len(pt.relative) > 0 {
pt.pwd = pt.root + pt.pathSeparator + pt.relative
return
}
@ -163,11 +181,14 @@ func (pt *Path) Init(props properties.Properties, env platform.Environment) {
func (pt *Path) setStyle() {
if len(pt.relative) == 0 {
pt.Path = pt.root
if strings.HasSuffix(pt.Path, ":") {
pt.Path += pt.getFolderSeparator()
}
return
}
switch style := pt.props.GetString(properties.Style, Agnoster); style {
case Agnoster:
pt.Path = pt.getAgnosterPath()
@ -228,35 +249,40 @@ func (pt *Path) getMaxWidth() int {
func (pt *Path) getFolderSeparator() string {
separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "")
if len(separatorTemplate) == 0 {
separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
separator := pt.props.GetString(FolderSeparatorIcon, pt.pathSeparator)
// if empty, use the default separator
if len(separator) == 0 {
return pt.env.PathSeparator()
return pt.pathSeparator
}
return separator
}
tmpl := &template.Text{
Template: separatorTemplate,
Context: pt,
Env: pt.env,
}
text, err := tmpl.Render()
if err != nil {
pt.env.Error(err)
}
if len(text) == 0 {
return pt.env.PathSeparator()
return pt.pathSeparator
}
return text
}
func (pt *Path) getMixedPath() string {
var buffer strings.Builder
threshold := int(pt.props.GetFloat64(MixedThreshold, 4))
folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator()
if pt.root != pt.env.PathSeparator() {
if pt.root != pt.pathSeparator {
pt.Folders = append(Folders{{Name: pt.root}}, pt.Folders...)
}
@ -276,7 +302,7 @@ func (pt *Path) getMixedPath() string {
func (pt *Path) getAgnosterPath() string {
folderIcon := pt.props.GetString(FolderIcon, "..")
if pt.root == pt.env.PathSeparator() {
if pt.root == pt.pathSeparator {
pt.root = pt.Folders[0].Name
pt.Folders = pt.Folders[1:]
}
@ -303,7 +329,7 @@ func (pt *Path) getAgnosterPath() string {
func (pt *Path) getAgnosterLeftPath() string {
folderIcon := pt.props.GetString(FolderIcon, "..")
if pt.root == pt.env.PathSeparator() {
if pt.root == pt.pathSeparator {
pt.root = pt.Folders[0].Name
pt.Folders = pt.Folders[1:]
}
@ -341,7 +367,7 @@ func (pt *Path) getRelevantLetter(folder *Folder) string {
}
func (pt *Path) getLetterPath() string {
if pt.root == pt.env.PathSeparator() {
if pt.root == pt.pathSeparator {
pt.root = pt.Folders[0].Name
pt.Folders = pt.Folders[1:]
}
@ -370,7 +396,7 @@ func (pt *Path) getLetterPath() string {
func (pt *Path) getUniqueLettersPath(maxWidth int) string {
separator := pt.getFolderSeparator()
if pt.root == pt.env.PathSeparator() {
if pt.root == pt.pathSeparator {
pt.root = pt.Folders[0].Name
pt.Folders = pt.Folders[1:]
}
@ -424,7 +450,7 @@ func (pt *Path) getUniqueLettersPath(maxWidth int) string {
}
func (pt *Path) getAgnosterFullPath() string {
if pt.root == pt.env.PathSeparator() {
if pt.root == pt.pathSeparator {
pt.root = pt.Folders[0].Name
pt.Folders = pt.Folders[1:]
}
@ -450,14 +476,13 @@ func (pt *Path) getAgnosterShortPath() string {
return pt.getAgnosterFullPath()
}
pathSeparator := pt.env.PathSeparator()
splitPos := pathDepth - maxDepth
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 == pathSeparator {
if root == pt.pathSeparator {
root = pt.Folders[0].Name
room--
}
@ -502,18 +527,22 @@ func (pt *Path) replaceMappedLocations() (string, string) {
if len(key) == 0 {
continue
}
tmpl := &template.Text{
Template: key,
Context: pt,
Env: pt.env,
}
path, err := tmpl.Render()
if err != nil {
pt.env.Error(err)
}
if len(path) == 0 {
continue
}
mappedLocations[pt.normalize(path)] = val
}
@ -524,59 +553,71 @@ func (pt *Path) replaceMappedLocations() (string, string) {
for k := range 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)
pathSeparator := pt.env.PathSeparator()
for _, key := range keys {
keyRoot, keyRelative := pt.parsePath(key)
matchSubFolders := strings.HasSuffix(keyRelative, "*")
if matchSubFolders && len(keyRelative) > 1 {
keyRelative = keyRelative[0 : len(keyRelative)-1] // remove trailing /* or \*
}
if keyRoot != rootN || !strings.HasPrefix(relativeN, keyRelative) {
continue
}
value := mappedLocations[key]
overflow := relative[len(keyRelative):]
if len(overflow) == 0 {
// exactly match the full path
return value, ""
}
if len(keyRelative) == 0 {
// only match the root
return value, strings.Trim(relative, pathSeparator)
return value, strings.Trim(relative, pt.pathSeparator)
}
// match several prefix elements
if matchSubFolders || overflow[0:1] == pt.env.PathSeparator() {
return value, strings.Trim(overflow, pathSeparator)
if matchSubFolders || overflow[0:1] == pt.pathSeparator {
return value, strings.Trim(overflow, pt.pathSeparator)
}
}
return root, strings.Trim(relative, pathSeparator)
return root, strings.Trim(relative, pt.pathSeparator)
}
func (pt *Path) normalizePath(path string) string {
if pt.env.GOOS() != platform.WINDOWS {
if pt.env.GOOS() != platform.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 ovoid replacing color codes
clean = append(clean, 92) // 92 == \
continue
}
clean = append(clean, char)
}
return string(clean)
}
@ -586,21 +627,34 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
if len(inputPath) == 0 {
return root, path
}
separator := pt.env.PathSeparator()
if pt.cygPath {
path, err := pt.env.RunCommand("cygpath", "-u", inputPath)
if len(path) != 0 {
inputPath = path
pt.pathSeparator = "/"
}
if err != nil {
pt.cygPath = false
pt.windowsPath = true
}
}
clean := func(path string) string {
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), path)
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(separator)
s.WriteString(pt.pathSeparator)
}
}
return s.String()
}
if pt.env.GOOS() == platform.WINDOWS {
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)
@ -610,14 +664,18 @@ func (pt *Path) parsePath(inputPath string) (string, string) {
return root, path
}
}
s := strings.SplitAfterN(inputPath, separator, 2)
s := strings.SplitAfterN(inputPath, pt.pathSeparator, 2)
root = s[0]
if pt.env.GOOS() == platform.WINDOWS {
root = strings.TrimSuffix(root, separator)
if pt.windowsPath {
root = strings.TrimSuffix(root, pt.pathSeparator)
}
if len(s) == 2 {
path = clean(s[1])
}
return root, path
}
@ -626,6 +684,11 @@ func (pt *Path) normalize(inputPath string) string {
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || platform.IsPathSeparator(pt.env, normalized[1])) {
normalized = pt.env.Home() + normalized[1:]
}
if pt.cygPath {
return normalized
}
switch pt.env.GOOS() {
case platform.WINDOWS:
normalized = pt.normalizePath(normalized)
@ -633,11 +696,12 @@ func (pt *Path) normalize(inputPath string) string {
case platform.DARWIN:
normalized = strings.ToLower(normalized)
}
return normalized
}
func (pt *Path) replaceFolderSeparators(pwd string) string {
defaultSeparator := pt.env.PathSeparator()
defaultSeparator := pt.pathSeparator
folderSeparator := pt.getFolderSeparator()
if folderSeparator == defaultSeparator {
return pwd
@ -685,7 +749,7 @@ func (pt *Path) colorizePath(root string, elements []string) string {
root = fmt.Sprintf(leftFormat, root)
builder.WriteString(colorizeElement(root))
if root != pt.env.PathSeparator() && len(root) != 0 {
if root != pt.pathSeparator && len(root) != 0 {
builder.WriteString(colorizeSeparator())
}
@ -732,18 +796,18 @@ func (pt *Path) splitPath() Folders {
folders := []string{}
if len(pt.relative) != 0 {
folders = strings.Split(pt.relative, pt.env.PathSeparator())
folders = strings.Split(pt.relative, pt.pathSeparator)
}
folderFormatMap := pt.makeFolderFormatMap()
getCurrentPath := func() string {
if pt.root == "~" {
return pt.env.Home() + pt.env.PathSeparator()
return pt.env.Home() + pt.pathSeparator
}
if pt.env.GOOS() == platform.WINDOWS {
return pt.root + pt.env.PathSeparator()
if pt.windowsPath {
return pt.root + pt.pathSeparator
}
return pt.root
@ -763,7 +827,7 @@ func (pt *Path) splitPath() Folders {
result = append(result, &Folder{Name: folder, Path: currentPath, Display: display})
currentPath += pt.env.PathSeparator()
currentPath += pt.pathSeparator
display = false
}

View file

@ -1,6 +1,7 @@
package segments
import (
"errors"
"strings"
"testing"
@ -170,6 +171,9 @@ func TestAgnosterPathStyles(t *testing.T) {
HomeIcon string
FolderSeparatorIcon string
GOOS string
Shell string
Cygpath string
CygpathError error
MaxDepth int
MaxWidth int
HideRootLocation bool
@ -417,6 +421,28 @@ func TestAgnosterPathStyles(t *testing.T) {
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "c > .. > foo > .. > man",
HomePath: homeDirWindows,
Pwd: "C:\\Users\\foo\\foobar\\man",
GOOS: platform.WINDOWS,
Shell: shell.BASH,
Cygpath: "/c/Users/foo/foobar/man",
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: Mixed,
Expected: "C: > .. > foo > .. > man",
HomePath: homeDirWindows,
Pwd: "C:\\Users\\foo\\foobar\\man",
GOOS: platform.WINDOWS,
Shell: shell.BASH,
CygpathError: errors.New("oh no"),
PathSeparator: "\\",
FolderSeparatorIcon: " > ",
},
{
Style: AgnosterFull,
@ -743,10 +769,20 @@ func TestAgnosterPathStyles(t *testing.T) {
PSWD: tc.Pswd,
}
env.On("Flags").Return(args)
env.On("Shell").Return(shell.PWSH)
if len(tc.Shell) == 0 {
tc.Shell = shell.PWSH
}
env.On("Shell").Return(tc.Shell)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
displayCygpath := tc.GOOS == platform.WINDOWS && tc.Shell == shell.BASH
if displayCygpath {
env.On("RunCommand", "cygpath", []string{"-u", tc.Pwd}).Return(tc.Cygpath, tc.CygpathError)
env.On("RunCommand", "cygpath", mock2.Anything).Return("brrrr", nil)
}
path := &Path{
env: env,
props: properties.Map{
@ -755,6 +791,7 @@ func TestAgnosterPathStyles(t *testing.T) {
MaxDepth: tc.MaxDepth,
MaxWidth: tc.MaxWidth,
HideRootLocation: tc.HideRootLocation,
DisplayCygpath: displayCygpath,
},
}
@ -917,17 +954,22 @@ func TestFullPathCustomMappedLocations(t *testing.T) {
env := new(mock.MockedEnvironment)
env.On("Home").Return(homeDir)
env.On("Pwd").Return(tc.Pwd)
if len(tc.GOOS) == 0 {
tc.GOOS = platform.DARWIN
}
env.On("GOOS").Return(tc.GOOS)
if len(tc.PathSeparator) == 0 {
tc.PathSeparator = "/"
}
env.On("PathSeparator").Return(tc.PathSeparator)
args := &platform.Flags{
PSWD: tc.Pwd,
}
env.On("Flags").Return(args)
env.On("Shell").Return(shell.GENERIC)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
@ -936,6 +978,7 @@ func TestFullPathCustomMappedLocations(t *testing.T) {
"HOME": "/a/b/c",
},
})
path := &Path{
env: env,
props: properties.Map{
@ -944,6 +987,7 @@ func TestFullPathCustomMappedLocations(t *testing.T) {
MappedLocations: tc.MappedLocations,
},
}
path.setPaths()
path.setStyle()
got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path)
@ -1414,24 +1458,29 @@ func TestGetFolderSeparator(t *testing.T) {
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("PathSeparator").Return("/")
env.On("Error", mock2.Anything)
env.On("Debug", mock2.Anything)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
path := &Path{
env: env,
env: env,
pathSeparator: "/",
}
props := properties.Map{}
if len(tc.FolderSeparatorTemplate) > 0 {
props[FolderSeparatorTemplate] = tc.FolderSeparatorTemplate
}
if len(tc.FolderSeparatorIcon) > 0 {
props[FolderSeparatorIcon] = tc.FolderSeparatorIcon
}
env.On("TemplateCache").Return(&platform.TemplateCache{
Env: make(map[string]string),
Shell: "bash",
})
path.props = props
got := path.getFolderSeparator()
assert.Equal(t, tc.Expected, got)
@ -1558,18 +1607,21 @@ func TestSplitPath(t *testing.T) {
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)
env.On("GOOS").Return(tc.GOOS)
path := &Path{
env: env,
props: properties.Map{
GitDirFormat: tc.GitDirFormat,
},
root: tc.Root,
relative: tc.Relative,
root: tc.Root,
relative: tc.Relative,
pathSeparator: "/",
windowsPath: tc.GOOS == platform.WINDOWS,
}
got := path.splitPath()
assert.Equal(t, tc.Expected, got, tc.Case)
}

View file

@ -2545,6 +2545,12 @@
"title": "The format to use on a git root directory",
"description": "Golang string format to apply to the .git folder",
"default": ""
},
"display_cygpath": {
"type": "boolean",
"title": "Display the Cygwin (Linux) style path",
"description": "Display the Cygwin (Linux) style path using cygpath -u $PWD.",
"default": false
}
}
}

View file

@ -49,6 +49,7 @@ import Config from "@site/src/components/Config.js";
| `left_format` | `string` | `%s` | format to use on the firstfolder of the path - defaults to `edge_format` |
| `right_format` | `string` | `%s` | format to use on the last folder of the path - defaults to `edge_format` |
| `gitdir_format` | `string` | | format to use for a git root directory |
| `display_cygpath` | `boolean` | `false` | display the Cygwin style path using `cygpath -u $PWD` |
## Mapped Locations