feat(path): flexible matching for mapped-locations

This commit is contained in:
Aaron Sherber 2021-09-18 16:03:00 -04:00 committed by GitHub
parent e1be81e658
commit 5db251ce53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 8 deletions

View file

@ -43,12 +43,12 @@ Display the current path.
## Mapped Locations
Allows you to override a location with an icon. It validates if the current path **starts with** the value and replaces
it with the icon if there's a match. To avoid issues with nested overrides, Oh my posh will sort the list of mapped
it with the icon if there's a match. To avoid issues with nested overrides, Oh My Posh will sort the list of mapped
locations before doing a replacement.
- mapped_locations_enabled: `boolean` - replace known locations in the path with the replacements before applying the
style. defaults to `true`
- mapped_locations: `map[string]string` - custom glyph/text for specific paths. Works regardless of the `mapped_locations_enabled`
style - defaults to `true`
- mapped_locations: `object` - custom glyph/text for specific paths. Works regardless of the `mapped_locations_enabled`
setting.
For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do the following:
@ -59,6 +59,16 @@ For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do t
}
```
### Notes
- Oh My Posh will accept both `/` and `\` as path separators for a mapped location and will match regardless of which
is used by the current operating system.
- 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
`C:\Users\Bill\Foo` or `C:\Users\Bill\foo` on Windows but only `/home/bill/Foo` on Linux.
## Style
Style sets the way the path is displayed. Based on previous experience and popular themes, there are 5 flavors.

View file

@ -220,6 +220,19 @@ func (pt *path) getPwd() string {
return pwd
}
func (pt *path) normalize(inputPath string) string {
normalized := inputPath
if strings.HasPrefix(inputPath, "~") {
normalized = pt.env.homeDir() + normalized[1:]
}
normalized = strings.ReplaceAll(normalized, "\\", "/")
goos := pt.env.getRuntimeGOOS()
if goos == windowsPlatform || goos == darwinPlatform {
normalized = strings.ToLower(normalized)
}
return normalized
}
func (pt *path) replaceMappedLocations(pwd string) string {
if strings.HasPrefix(pwd, "Microsoft.PowerShell.Core\\FileSystem::") {
pwd = strings.Replace(pwd, "Microsoft.PowerShell.Core\\FileSystem::", "", 1)
@ -229,14 +242,14 @@ func (pt *path) replaceMappedLocations(pwd string) string {
if pt.props.getBool(MappedLocationsEnabled, true) {
mappedLocations["HKCU:"] = pt.props.getString(WindowsRegistryIcon, "\uF013")
mappedLocations["HKLM:"] = pt.props.getString(WindowsRegistryIcon, "\uF013")
mappedLocations[pt.env.homeDir()] = pt.props.getString(HomeIcon, "~")
mappedLocations[pt.normalize(pt.env.homeDir())] = pt.props.getString(HomeIcon, "~")
}
// 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 {
mappedLocations[key] = val
mappedLocations[pt.normalize(key)] = val
}
// sort map keys in reverse order
@ -250,9 +263,11 @@ func (pt *path) replaceMappedLocations(pwd string) string {
}
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
for _, value := range keys {
if strings.HasPrefix(pwd, value) {
return strings.Replace(pwd, value, mappedLocations[value], 1)
normalizedPwd := pt.normalize(pwd)
for _, key := range keys {
if strings.HasPrefix(normalizedPwd, key) {
value := mappedLocations[key]
return value + pwd[len(key):]
}
}
return pwd

View file

@ -220,6 +220,7 @@ func TestRootLocationHome(t *testing.T) {
}
env.On("getArgs", nil).Return(args)
env.On("getPathSeperator", nil).Return(tc.PathSeperator)
env.On("getRuntimeGOOS", nil).Return("")
path := &path{
env: env,
props: props,
@ -247,6 +248,7 @@ func TestPathDepthMultipleLevelsDeep(t *testing.T) {
}
env := new(MockedEnvironment)
env.On("getPathSeperator", nil).Return("/")
env.On("getRunteGOOS", nil).Return("")
path := &path{
env: env,
}
@ -500,12 +502,82 @@ func TestGetFullPath(t *testing.T) {
}
}
func TestGetFullPathCustomMappedLocations(t *testing.T) {
cases := []struct {
Pwd string
MappedLocations map[string]string
Expected string
}{
{Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b/c/d": "#"}, Expected: "#"},
{Pwd: "/a/b/c/d", MappedLocations: map[string]string{"\\a\\b": "#"}, Expected: "#/c/d"},
{Pwd: "\\a\\b\\c\\d", MappedLocations: map[string]string{"\\a\\b": "#"}, Expected: "#\\c\\d"},
{Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b": "#"}, Expected: "#/c/d"},
{Pwd: "/a/b/c/d", MappedLocations: map[string]string{"/a/b": "/e/f"}, Expected: "/e/f/c/d"},
{Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"~\\a\\b": "#"}, Expected: "#/c/d"},
{Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"~/a/b": "#"}, Expected: "#/c/d"},
{Pwd: "/a/usr/home/b/c/d", MappedLocations: map[string]string{"/a~": "#"}, Expected: "/a/usr/home/b/c/d"},
{Pwd: "/usr/home/a/b/c/d", MappedLocations: map[string]string{"/a/b": "#"}, Expected: "/usr/home/a/b/c/d"},
}
for _, tc := range cases {
env := new(MockedEnvironment)
env.On("getPathSeperator", nil).Return("/")
env.On("homeDir", nil).Return("/usr/home")
env.On("getcwd", nil).Return(tc.Pwd)
env.On("getRuntimeGOOS", nil).Return("")
args := &args{
PSWD: &tc.Pwd,
}
env.On("getArgs", nil).Return(args)
path := &path{
env: env,
props: &properties{
values: map[Property]interface{}{
MappedLocationsEnabled: false,
MappedLocations: tc.MappedLocations,
},
},
}
got := path.getFullPath()
assert.Equal(t, tc.Expected, got)
}
}
func TestNormalizePath(t *testing.T) {
cases := []struct {
Input string
GOOS string
Expected string
}{
{Input: "C:\\Users\\Bob\\Foo", GOOS: linuxPlatform, Expected: "C:/Users/Bob/Foo"},
{Input: "C:\\Users\\Bob\\Foo", GOOS: windowsPlatform, Expected: "c:/users/bob/foo"},
{Input: "~\\Bob\\Foo", GOOS: linuxPlatform, Expected: "/usr/home/Bob/Foo"},
{Input: "~\\Bob\\Foo", GOOS: windowsPlatform, Expected: "/usr/home/bob/foo"},
{Input: "/foo/~/bar", GOOS: linuxPlatform, Expected: "/foo/~/bar"},
{Input: "/foo/~/bar", GOOS: windowsPlatform, Expected: "/foo/~/bar"},
{Input: "~/baz", GOOS: linuxPlatform, Expected: "/usr/home/baz"},
{Input: "~/baz", GOOS: windowsPlatform, Expected: "/usr/home/baz"},
}
for _, tc := range cases {
env := new(MockedEnvironment)
env.On("homeDir", nil).Return("/usr/home")
env.On("getRuntimeGOOS", nil).Return(tc.GOOS)
pt := &path{
env: env,
}
got := pt.normalize(tc.Input)
assert.Equal(t, tc.Expected, got)
}
}
func TestGetFolderPathCustomMappedLocations(t *testing.T) {
pwd := "/a/b/c/d"
env := new(MockedEnvironment)
env.On("getPathSeperator", nil).Return("/")
env.On("homeDir", nil).Return("/usr/home")
env.On("getcwd", nil).Return(pwd)
env.On("getRuntimeGOOS", nil).Return("")
args := &args{
PSWD: &pwd,
}
@ -536,6 +608,7 @@ func testWritePathInfo(home, pwd, pathSeparator string) string {
env.On("homeDir", nil).Return(home)
env.On("getPathSeperator", nil).Return(pathSeparator)
env.On("getcwd", nil).Return(pwd)
env.On("getRuntimeGOOS", nil).Return("")
args := &args{
PSWD: &pwd,
}
@ -635,6 +708,7 @@ func TestGetPwd(t *testing.T) {
env.On("getPathSeperator", nil).Return("/")
env.On("homeDir", nil).Return("/usr/home")
env.On("getcwd", nil).Return(tc.Pwd)
env.On("getRuntimeGOOS", nil).Return("")
args := &args{
PSWD: &tc.Pswd,
}