diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 2ba8837844cb..aaec735d85a2 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -293,8 +293,9 @@ will not be rendered when in one of the excluded locations. ] ``` -You can also specify a [regular expression][regex] to create folder wildcards. -In the sample below, any folders inside the `/Users/posh/Projects` path will be matched. +The strings specified in these properties are evaluated as [regular expressions][regex]. You +can use any valid regular expression construct, but the regular expression must match the entire directory +name. The following will match `/Users/posh/Projects/Foo` but not `/home/Users/posh/Projects/Foo`. ```json "include_folders": [ @@ -313,16 +314,17 @@ You can also combine these properties: ] ``` -Note for Windows users: Windows directory separators should be specified as 4 backslashes. +##### Notes -```json -"include_folders": [ - "C:\\\\Projects.*" -], -"exclude_folders": [ - "C:\\\\Projects\\\\secret-project.*" -] -``` +- Oh My Posh will accept both `/` and `\` as path separators for a folder and will match regardless of which +is used by the current operating system. +- Because the strings are evaluated as regular expressions, if you want to use a `\` in a Windows +directory name, you need to specify it as `\\\\`. +- The character `~` at the start of a specified folder will match the user's home directory. +- The comparison 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. ### Colors diff --git a/src/segment.go b/src/segment.go index 012d885f8c3d..020c680de707 100644 --- a/src/segment.go +++ b/src/segment.go @@ -3,6 +3,7 @@ package main import ( "errors" "fmt" + "strings" "time" ) @@ -176,9 +177,20 @@ func (segment *Segment) cwdExcluded(cwd string) bool { } func (segment *Segment) cwdMatchesOneOf(cwd string, regexes []string) bool { + normalizedCwd := strings.ReplaceAll(cwd, "\\", "/") + normalizedHomeDir := strings.ReplaceAll(segment.env.homeDir(), "\\", "/") + for _, element := range regexes { - pattern := fmt.Sprintf("^%s$", element) - matched := matchString(pattern, cwd) + normalizedElement := strings.ReplaceAll(element, "\\\\", "/") + if strings.HasPrefix(normalizedElement, "~") { + normalizedElement = normalizedHomeDir + normalizedElement[1:] + } + pattern := fmt.Sprintf("^%s$", normalizedElement) + goos := segment.env.getRuntimeGOOS() + if goos == windowsPlatform || goos == darwinPlatform { + pattern = "(?i)" + pattern + } + matched := matchString(pattern, normalizedCwd) if matched { return true } diff --git a/src/segment_test.go b/src/segment_test.go index 7765162db465..5cf71ae831a4 100644 --- a/src/segment_test.go +++ b/src/segment_test.go @@ -81,11 +81,15 @@ func TestShouldIncludeFolder(t *testing.T) { {Case: "Include Mismatch / Exclude Mismatch", IncludeFolders: []string{"zProjects.*"}, ExcludeFolders: []string{"Projects/nope"}, Expected: false}, } for _, tc := range cases { + env := new(MockedEnvironment) + env.On("getRuntimeGOOS", nil).Return(linuxPlatform) + env.On("homeDir", nil).Return("") segment := &Segment{ Properties: map[Property]interface{}{ IncludeFolders: tc.IncludeFolders, ExcludeFolders: tc.ExcludeFolders, }, + env: env, } got := segment.shouldIncludeFolder(cwd) assert.Equal(t, tc.Expected, got, tc.Case) @@ -93,10 +97,14 @@ func TestShouldIncludeFolder(t *testing.T) { } func TestShouldIncludeFolderRegexInverted(t *testing.T) { + env := new(MockedEnvironment) + env.On("getRuntimeGOOS", nil).Return(linuxPlatform) + env.On("homeDir", nil).Return("") segment := &Segment{ Properties: map[Property]interface{}{ ExcludeFolders: []string{"(?!Projects[\\/]).*"}, }, + env: env, } // detect panic(thrown by MustCompile) defer func() { @@ -109,10 +117,14 @@ func TestShouldIncludeFolderRegexInverted(t *testing.T) { } func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) { + env := new(MockedEnvironment) + env.On("getRuntimeGOOS", nil).Return(linuxPlatform) + env.On("homeDir", nil).Return("") segment := &Segment{ Properties: map[Property]interface{}{ ExcludeFolders: []string{"(?!Projects/).*"}, }, + env: env, } // detect panic(thrown by MustCompile) defer func() { @@ -196,3 +208,39 @@ func TestGetColors(t *testing.T) { assert.Equal(t, tc.ExpectedColor, color, tc.Case) } } + +func TestCwdMatchesOneOf(t *testing.T) { + cases := []struct { + GOOS string + HomeDir string + Cwd string + Pattern string + Expected bool + }{ + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill", Pattern: "/home/bill", Expected: true}, + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/foo", Expected: true}, + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/Foo", Expected: false}, + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~\\\\foo", Expected: true}, + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo/bar", Pattern: "~/fo.*", Expected: true}, + {GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/fo\\w", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:\\\\Users\\\\Bill", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:/Users/Bill", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "c:/users/bill", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "~", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/Foo", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/foo", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo\\Bar", Pattern: "~/fo.*", Expected: true}, + {GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/fo\\w", Expected: true}, + } + + for _, tc := range cases { + env := new(MockedEnvironment) + env.On("getRuntimeGOOS", nil).Return(tc.GOOS) + env.On("homeDir", nil).Return(tc.HomeDir) + segment := &Segment{ + env: env, + } + got := segment.cwdMatchesOneOf(tc.Cwd, []string{tc.Pattern}) + assert.Equal(t, tc.Expected, got) + } +}