Repository: JanDeDobbeleer/oh-my-posh
Pull request #905 (2021-08-18 14:47:38)

feat(path): add 'max_depth' option for 'agnoster_short'
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added / updated (for bug fixes/features)

### Description

Added a new style for the path segment, almost like Agnoster Short but only shortens the number of folders exceeding `max_depth`.

Not sure if `Shortened` is the best possible name for it, but it's the best I could think of.


JanDeDobbeleer: Looking at the almost identical styles, wouldn't it make sense to add that switch (max_depth) to Agnoster? If it resolves to anything <= 0 it behaves like Agnoster now. 
NervousNerve: Yeah, you're probably right. I was hesitant to alter the functionality of the classic Agnoster, but if I have your blessing.. 🙂
So default for max_depth will be 0, which will leave it working as before.
NervousNerve: Ok, I moved the functionality into Agnoster Short. Made `max_depth` default to 1 instead, because there should always be at least one folder displayed at the end, but anything less behaves the same.
JanDeDobbeleer: @NervousNerve fails on the linter. Can you check and adjust?
NervousNerve: Forgot about gofm, so that should be fixed now.
I tried running the linter locally, but it just complains about a bunch of stuff I didn't touch, so I don't know what that's about.
```
config.go:73:2: undeclared name: `config` (typecheck)
        config.AddDriver(yaml.Driver)
        ^
config.go:74:2: undeclared name: `config` (typecheck)
        config.AddDriver(json.Driver)
        ^
config.go:75:2: undeclared name: `config` (typecheck)
        config.AddDriver(toml.Driver)
        ^
template.go:51:43: undeclared name: `sprig` (typecheck)
        tmpl, err := template.New("title").Funcs(sprig.TxtFuncMap()).Parse(t.Template)
                                                 ^
console_title_test.go:57:7: env.On undefined (type *MockedEnvironment has no field or method On) (typecheck)
                env.On("getcwd", nil).Return(tc.Cwd)
                    ^
console_title_test.go:58:7: env.On undefined (type *MockedEnvironment has no field or method On) (typecheck)
                env.On("homeDir", nil).Return("/usr/home")
                    ^
console_title_test.go:59:7: env.On undefined (type *MockedEnvironment has no field or method On) (typecheck)
                env.On("getPathSeperator", nil).Return(tc.PathSeperator)
                    ^
segment_git_test.go:132:4: m.On undefined (type *MockedEnvironment has no field or method On) (typecheck)
        m.On("runCommand", "git", args).Return(returnValue, nil)
          ^
segment_path_test.go:18:14: env.Called undefined (type *MockedEnvironment has no field or method Called) (typecheck)
        args := env.Called(key)
                    ^
segment_path_test.go:23:14: env.Called undefined (type *MockedEnvironment has no field or method Called) (typecheck)
        args := env.Called(nil)
                    ^
segment_path_test.go:28:14: env.Called undefined (type *MockedEnvironment has no field or method Called) (typecheck)
        args := env.Called(nil)
                    ^
config.go:14:2: "github.com/gookit/config/v2" imported but not used (typecheck)
        "github.com/gookit/config/v2"
        ^
main.go:11:2: "github.com/gookit/config/v2" imported but not used (typecheck)
        "github.com/gookit/config/v2"
        ^
template.go:10:2: "github.com/Masterminds/sprig/v3" imported but not used (typecheck)
        "github.com/Masterminds/sprig/v3"
        ^
segment_path_test.go:7:2: "github.com/gookit/config/v2" imported but not used (typecheck)
        "github.com/gookit/config/v2"
        ^
../../../../../../usr/local/go/src/runtime/cgo/cgo.go:34:8: could not import C (cgo preprocessing failed) (typecheck)
import "C"
       ^
```
JanDeDobbeleer: @NervousNerve do you have all dependencies locally (running go test or go build should resolves that).
NervousNerve: @JanDeDobbeleer Thank you for your help and patience. That was indeed the issue. I didn't have gcc installed.
Linter was complaining about long lines in my test cases, which should be fixed now.

Patch:
diff --git a/docs/docs/segment-path.md b/docs/docs/segment-path.md
index c81fe3fa..97d2f81c 100644
--- a/docs/docs/segment-path.md
+++ b/docs/docs/segment-path.md
@@ -38,6 +38,7 @@ Display the current path.
 - mixed_threshold: `number` - the maximum length of a path segment that will be displayed when using `Mixed` -
   defaults to `4`
 - stack_count_enabled: `boolean` - displays the stack count when using pushd/popd - defaults to `false`
+- max_depth: `number` - maximum path depth to display before shortening when using `Agnoster Short` - defaults to `1`
 
 ## Mapped Locations
 
@@ -82,7 +83,8 @@ Renders each folder name separated by the `folder_separator_icon`.
 
 ### Agnoster Short
 
-When more than 1 level deep, it renders one `folder_icon` followed by the name of the current folder separated by the `folder_separator_icon`.
+When more than `max_depth` levels deep, it renders one `folder_icon` followed by the names of the last `max_depth` folders,
+separated by the `folder_separator_icon`.
 
 ### Full
 
diff --git a/src/segment_path.go b/src/segment_path.go
index a3504958..b66d3c62 100644
--- a/src/segment_path.go
+++ b/src/segment_path.go
@@ -45,6 +45,8 @@ const (
 	MappedLocationsEnabled Property = "mapped_locations_enabled"
 	// StackCountEnabled enables the stack count display
 	StackCountEnabled Property = "stack_count_enabled"
+	// Maximum path depth to display whithout shortening
+	MaxDepth Property = "max_depth"
 )
 
 func (pt *path) enabled() bool {
@@ -174,20 +176,28 @@ func (pt *path) getAgnosterFullPath() string {
 }
 
 func (pt *path) getAgnosterShortPath() string {
+	pwd := pt.getPwd()
+	pathDepth := pt.pathDepth(pwd)
+	maxDepth := pt.props.getInt(MaxDepth, 1)
+	if maxDepth < 1 {
+		maxDepth = 1
+	}
+	if pathDepth <= maxDepth {
+		return pt.getAgnosterFullPath()
+	}
 	pathSeparator := pt.env.getPathSeperator()
 	folderSeparator := pt.props.getString(FolderSeparatorIcon, pathSeparator)
 	folderIcon := pt.props.getString(FolderIcon, "..")
 	root := pt.rootLocation()
-	pwd := pt.getPwd()
-	base := base(pwd, pt.env)
-	pathDepth := pt.pathDepth(pwd)
-	if pathDepth <= 0 {
-		return root
-	}
-	if pathDepth == 1 {
-		return fmt.Sprintf("%s%s%s", root, folderSeparator, base)
+	splitted := strings.Split(pwd, pathSeparator)
+	fullPathDepth := len(splitted)
+	splitPos := fullPathDepth - maxDepth
+	var buffer strings.Builder
+	buffer.WriteString(fmt.Sprintf("%s%s%s", root, folderSeparator, folderIcon))
+	for i := splitPos; i < fullPathDepth; i++ {
+		buffer.WriteString(fmt.Sprintf("%s%s", folderSeparator, splitted[i]))
 	}
-	return fmt.Sprintf("%s%s%s%s%s", root, folderSeparator, folderIcon, folderSeparator, base)
+	return buffer.String()
 }
 
 func (pt *path) getFullPath() string {
diff --git a/themes/schema.json b/themes/schema.json
index f2e23301..fd34d0f1 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1232,6 +1232,12 @@
                     "title": "Mapped Locations",
                     "description": "Custom glyph/text for specific paths",
                     "default": {}
+                  },
+                  "max_depth": {
+                    "type": "integer",
+                    "title": "Maximum Depth",
+                    "description": "Maximum path depth to display whithout shortening",
+                    "default": 1
                   }
                 },
                 "mapped_locations_enabled": {


Test patch:
diff --git a/src/segment_path_test.go b/src/segment_path_test.go
index 56a941c3..db395bfc 100644
--- a/src/segment_path_test.go
+++ b/src/segment_path_test.go
@@ -282,6 +282,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 		FolderSeparatorIcon string
 		Style               string
 		GOOS                string
+		MaxDepth            int
 	}{
 		{Style: AgnosterFull, Expected: "usr > location > whatever", HomePath: "/usr/home", Pwd: "/usr/location/whatever", PathSeperator: "/", FolderSeparatorIcon: " > "},
 		{Style: AgnosterShort, Expected: "usr > .. > man", HomePath: "/usr/home", Pwd: "/usr/location/whatever/man", PathSeperator: "/", FolderSeparatorIcon: " > "},
@@ -291,6 +292,48 @@ func TestAgnosterPathStyles(t *testing.T) {
 		{Style: AgnosterShort, Expected: "", HomePath: homeBillWindows, Pwd: "/", PathSeperator: "/", FolderSeparatorIcon: " > "},
 		{Style: AgnosterShort, Expected: "foo", HomePath: homeBillWindows, Pwd: "/foo", PathSeperator: "/", FolderSeparatorIcon: " > "},
 
+		{Style: AgnosterShort, Expected: "usr > .. > bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeperator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2},
+		{Style: AgnosterShort, Expected: "usr > foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/foo/bar/man", PathSeperator: "/", FolderSeparatorIcon: " > ", MaxDepth: 3},
+		{Style: AgnosterShort, Expected: "~ > .. > bar > man", HomePath: "/usr/home", Pwd: "/usr/home/foo/bar/man", PathSeperator: "/", FolderSeparatorIcon: " > ", MaxDepth: 2},
+		{Style: AgnosterShort, Expected: "~ > foo > bar > man", HomePath: "/usr/home", Pwd: "/usr/home/foo/bar/man", PathSeperator: "/", FolderSeparatorIcon: " > ", MaxDepth: 3},
+
+		{
+			Style:               AgnosterShort,
+			Expected:            "C: > .. > bar > man",
+			HomePath:            homeBillWindows,
+			Pwd:                 "C:\\usr\\foo\\bar\\man",
+			PathSeperator:       "\\",
+			FolderSeparatorIcon: " > ",
+			MaxDepth:            2,
+		},
+		{
+			Style:               AgnosterShort,
+			Expected:            "C: > .. > foo > bar > man",
+			HomePath:            homeBillWindows,
+			Pwd:                 "C:\\usr\\foo\\bar\\man",
+			PathSeperator:       "\\",
+			FolderSeparatorIcon: " > ",
+			MaxDepth:            3,
+		},
+		{
+			Style:               AgnosterShort,
+			Expected:            "~ > .. > bar > man",
+			HomePath:            homeBillWindows,
+			Pwd:                 "C:\\Users\\Bill\\foo\\bar\\man",
+			PathSeperator:       "\\",
+			FolderSeparatorIcon: " > ",
+			MaxDepth:            2,
+		},
+		{
+			Style:               AgnosterShort,
+			Expected:            "~ > foo > bar > man",
+			HomePath:            homeBillWindows,
+			Pwd:                 "C:\\Users\\Bill\\foo\\bar\\man",
+			PathSeperator:       "\\",
+			FolderSeparatorIcon: " > ",
+			MaxDepth:            3,
+		},
+
 		{Style: AgnosterFull, Expected: "PSDRIVE: | src", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src", PathSeperator: "/", FolderSeparatorIcon: " | "},
 		{Style: AgnosterShort, Expected: "PSDRIVE: | .. | init", HomePath: homeBillWindows, Pwd: "/foo", Pswd: "PSDRIVE:/src/init", PathSeperator: "/", FolderSeparatorIcon: " | "},
 
@@ -318,6 +361,7 @@ func TestAgnosterPathStyles(t *testing.T) {
 				values: map[Property]interface{}{
 					FolderSeparatorIcon: tc.FolderSeparatorIcon,
 					Style:               tc.Style,
+					MaxDepth:            tc.MaxDepth,
 				},
 			},
 		}