Repository: JanDeDobbeleer/oh-my-posh
Pull request #636 (2021-04-12 08:59:14)

feat: show/hide stack count in path segment
### Prerequisites

- [x] I have read and understand the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [ ] Tests for the changes have been added (for bug fixes / features)
- [x] Docs have been added / updated (for bug fixes / features)

### Description

#634 
Adds the stack count in path segment when using pushd/popd

~~WIP. needs to make it work in Fish.~~

There is room for improvement(maybe with a template) but it works as requested for now.

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary


lnu: still need to add some tests

Patch:
diff --git a/docs/docs/segment-path.md b/docs/docs/segment-path.md
index 1ce73da2..fd8969ee 100644
--- a/docs/docs/segment-path.md
+++ b/docs/docs/segment-path.md
@@ -40,6 +40,7 @@ style. defaults to `true`
 - enable_hyperlink: `boolean` - displays an hyperlink for the path - defaults to `false`
 - 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`
 
 ## Style
 
diff --git a/src/environment.go b/src/environment.go
index 82276197..51506ca8 100644
--- a/src/environment.go
+++ b/src/environment.go
@@ -70,6 +70,7 @@ type environmentInfo interface {
 	doGet(url string) ([]byte, error)
 	hasParentFilePath(path string) (fileInfo *fileInfo, err error)
 	isWsl() bool
+	stackCount() int
 }
 
 type commandCache struct {
@@ -335,6 +336,13 @@ func (env *environment) hasParentFilePath(path string) (*fileInfo, error) {
 	}
 }
 
+func (env *environment) stackCount() int {
+	if *env.args.StackCount < 0 {
+		return 0
+	}
+	return *env.args.StackCount
+}
+
 func cleanHostName(hostName string) string {
 	garbage := []string{
 		".lan",
diff --git a/src/init/omp.bash b/src/init/omp.bash
index 02fd02b8..5a28e688 100644
--- a/src/init/omp.bash
+++ b/src/init/omp.bash
@@ -7,6 +7,7 @@ PS0='$(::OMP:: --millis > $TIMER_START)'
 function _omp_hook() {
     local ret=$?
 
+    stack_count=$((${#DIRSTACK[@]} - 1))
     omp_elapsed=-1
     if [[ -f $TIMER_START ]]; then
         omp_now=$(::OMP:: --millis)
@@ -14,7 +15,7 @@ function _omp_hook() {
         omp_elapsed=$((omp_now-omp_start_time))
         rm -f "$TIMER_START"
     fi
-    PS1="$(::OMP:: --config $POSH_THEME --shell bash --error $ret --execution-time $omp_elapsed)"
+    PS1="$(::OMP:: --config $POSH_THEME --shell bash --error $ret --execution-time $omp_elapsed --stack-count $stack_count)"
 
     return $ret
 }
diff --git a/src/init/omp.fish b/src/init/omp.fish
index 03c4a885..3769aca9 100644
--- a/src/init/omp.fish
+++ b/src/init/omp.fish
@@ -1,6 +1,7 @@
 set -g posh_theme ::CONFIG::
 
 function fish_prompt
+    set -l stack_count (count $dirstack)
     set -l omp_duration "$CMD_DURATION$cmd_duration"
     # check if variable set, < 3.2 case
     if set -q omp_lastcommand; and test "$omp_lastcommand" = ""
@@ -14,7 +15,7 @@ function fish_prompt
       set -gx omp_last_status_generation $status_generation
     end
 
-    ::OMP:: --config $posh_theme --error $status --execution-time $omp_duration
+    ::OMP:: --config $posh_theme --error $status --execution-time $omp_duration --stack-count $stack_count
 end
 
 function postexec_omp --on-event fish_postexec
diff --git a/src/init/omp.ps1 b/src/init/omp.ps1
index b85deafc..49ef7ed2 100644
--- a/src/init/omp.ps1
+++ b/src/init/omp.ps1
@@ -65,6 +65,7 @@ function global:Initialize-ModuleSupport {
         }
     }
 
+    $stackCount = (Get-Location -Stack).Count
     $executionTime = -1
     $history = Get-History -ErrorAction Ignore -Count 1
     if ($null -ne $history -and $null -ne $history.EndExecutionTime -and $null -ne $history.StartExecutionTime -and $global:omp_lastHistoryId -ne $history.Id) {
@@ -76,7 +77,7 @@ function global:Initialize-ModuleSupport {
     $config = $global:PoshSettings.Theme
     $cleanPWD = $PWD.ProviderPath.TrimEnd("\")
     $cleanPSWD = $PWD.ToString().TrimEnd("\")
-    $standardOut = @(&$omp --error="$errorCode" --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time="$executionTime" --config="$config" 2>&1)
+    $standardOut = @(&$omp --error="$errorCode" --pwd="$cleanPWD" --pswd="$cleanPSWD" --execution-time="$executionTime" --stack-count="$stackCount" --config="$config" 2>&1)
     # the output can be multiline, joining these ensures proper rendering by adding line breaks with `n
     $standardOut -join "`n"
     $global:LASTEXITCODE = $realLASTEXITCODE
diff --git a/src/init/omp.zsh b/src/init/omp.zsh
index 0002a47f..a3274708 100644
--- a/src/init/omp.zsh
+++ b/src/init/omp.zsh
@@ -5,13 +5,14 @@ function omp_preexec() {
 }
 
 function omp_precmd() {
+  stack_count=${#dirstack[@]}
   omp_last_error=$?
   omp_elapsed=-1
   if [ $omp_start_time ]; then
     omp_now=$(::OMP:: --millis)
     omp_elapsed=$(($omp_now-$omp_start_time))
   fi
-  eval "$(::OMP:: --config $POSH_THEME --error $omp_last_error --execution-time $omp_elapsed --eval --shell zsh)"
+  eval "$(::OMP:: --config $POSH_THEME --error $omp_last_error --execution-time $omp_elapsed --stack-count $stack_count --eval --shell zsh)"
   unset omp_start_time
   unset omp_now
   unset omp_elapsed
diff --git a/src/main.go b/src/main.go
index 6ba5571d..6d0a00c0 100644
--- a/src/main.go
+++ b/src/main.go
@@ -56,6 +56,7 @@ type args struct {
 	Author        *string
 	CursorPadding *int
 	RPromptOffset *int
+	StackCount    *int
 }
 
 func main() {
@@ -136,6 +137,10 @@ func main() {
 			"rprompt-offset",
 			40,
 			"Offset the right prompt with x when using --export-img"),
+		StackCount: flag.Int(
+			"stack-count",
+			0,
+			"The current location stack count"),
 	}
 	flag.Parse()
 	env := &environment{}
diff --git a/src/segment_path.go b/src/segment_path.go
index 43c7526d..8b3f04f6 100644
--- a/src/segment_path.go
+++ b/src/segment_path.go
@@ -41,6 +41,8 @@ const (
 	MappedLocations Property = "mapped_locations"
 	// MappedLocationsEnabled enables overriding certain locations with an icon
 	MappedLocationsEnabled Property = "mapped_locations_enabled"
+	// StackCountEnabled enables the stack count display
+	StackCountEnabled Property = "stack_count_enabled"
 )
 
 func (pt *path) enabled() bool {
@@ -77,6 +79,11 @@ func (pt *path) string() string {
 		}
 		return fmt.Sprintf("[%s](file://%s)", formattedPath, cwd)
 	}
+
+	if pt.props.getBool(StackCountEnabled, false) && pt.env.stackCount() > 0 {
+		return fmt.Sprintf("%d %s", pt.env.stackCount(), formattedPath)
+	}
+
 	return formattedPath
 }
 
diff --git a/themes/schema.json b/themes/schema.json
index abbaea73..f8e76c1a 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -1124,6 +1124,12 @@
                     "title": "Enable hyperlink",
                     "description": "Displays an hyperlink for the current path",
                     "default": false
+                  },
+                  "stack_count_enabled": {
+                    "type": "boolean",
+                    "title": "Show/hide stack count",
+                    "description": "Displays the stack count when using pushd/popd",
+                    "default": false
                   }
                 }
               }


Test patch:
diff --git a/src/segment_path_test.go b/src/segment_path_test.go
index 2b3d18cf..d23712b0 100644
--- a/src/segment_path_test.go
+++ b/src/segment_path_test.go
@@ -132,6 +132,11 @@ func (env *MockedEnvironment) hasParentFilePath(path string) (*fileInfo, error)
 	return args.Get(0).(*fileInfo), args.Error(1)
 }
 
+func (env *MockedEnvironment) stackCount() int {
+	args := env.Called(nil)
+	return args.Int(0)
+}
+
 func (env *MockedEnvironment) isWsl() bool {
 	return false
 }
@@ -319,6 +324,8 @@ func TestGetFullPath(t *testing.T) {
 		DisableMappedLocations bool
 		GOOS                   string
 		PathSeparator          string
+		StackCount             int
+		StackCountEnabled      bool
 	}{
 		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", Expected: "/"},
 		{Style: Full, Pwd: "", Expected: ""},
@@ -350,6 +357,51 @@ func TestGetFullPath(t *testing.T) {
 
 		{Style: Folder, FolderSeparatorIcon: "\\", Pwd: "C:\\", Expected: "C:\\", PathSeparator: "\\", GOOS: windowsPlatform},
 		{Style: Full, FolderSeparatorIcon: "\\", Pwd: "C:\\Users\\Jan", Expected: "C:\\Users\\Jan", PathSeparator: "\\", GOOS: windowsPlatform},
+
+		// StackCountEnabled=true and StackCount=2
+		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: 2, Expected: "2 /"},
+		{Style: Full, Pwd: "", StackCountEnabled: true, StackCount: 2, Expected: "2 "},
+		{Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: 2, Expected: "2 /"},
+		{Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: 2, Expected: "2 ~"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 2, Expected: "2 ~/abc"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 2, Expected: "2 /usr/home/abc", DisableMappedLocations: true},
+		{Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: 2, Expected: "2 /a/b/c/d"},
+
+		// StackCountEnabled=false and StackCount=2
+		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: false, StackCount: 2, Expected: "/"},
+		{Style: Full, Pwd: "", StackCountEnabled: false, StackCount: 2, Expected: ""},
+		{Style: Full, Pwd: "/", StackCountEnabled: false, StackCount: 2, Expected: "/"},
+		{Style: Full, Pwd: "/usr/home", StackCountEnabled: false, StackCount: 2, Expected: "~"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: false, StackCount: 2, Expected: "~/abc"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: false, StackCount: 2, Expected: "/usr/home/abc", DisableMappedLocations: true},
+		{Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: false, StackCount: 2, Expected: "/a/b/c/d"},
+
+		// StackCountEnabled=true and StackCount=0
+		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: 0, Expected: "/"},
+		{Style: Full, Pwd: "", StackCountEnabled: true, StackCount: 0, Expected: ""},
+		{Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: 0, Expected: "/"},
+		{Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: 0, Expected: "~"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 0, Expected: "~/abc"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: 0, Expected: "/usr/home/abc", DisableMappedLocations: true},
+		{Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: 0, Expected: "/a/b/c/d"},
+
+		// StackCountEnabled=true and StackCount<0
+		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, StackCount: -1, Expected: "/"},
+		{Style: Full, Pwd: "", StackCountEnabled: true, StackCount: -1, Expected: ""},
+		{Style: Full, Pwd: "/", StackCountEnabled: true, StackCount: -1, Expected: "/"},
+		{Style: Full, Pwd: "/usr/home", StackCountEnabled: true, StackCount: -1, Expected: "~"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: -1, Expected: "~/abc"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, StackCount: -1, Expected: "/usr/home/abc", DisableMappedLocations: true},
+		{Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, StackCount: -1, Expected: "/a/b/c/d"},
+
+		// StackCountEnabled=true and StackCount not set
+		{Style: Full, FolderSeparatorIcon: "|", Pwd: "/", StackCountEnabled: true, Expected: "/"},
+		{Style: Full, Pwd: "", StackCountEnabled: true, Expected: ""},
+		{Style: Full, Pwd: "/", StackCountEnabled: true, Expected: "/"},
+		{Style: Full, Pwd: "/usr/home", StackCountEnabled: true, Expected: "~"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, Expected: "~/abc"},
+		{Style: Full, Pwd: "/usr/home/abc", StackCountEnabled: true, Expected: "/usr/home/abc", DisableMappedLocations: true},
+		{Style: Full, Pwd: "/a/b/c/d", StackCountEnabled: true, Expected: "/a/b/c/d"},
 	}
 
 	for _, tc := range cases {
@@ -361,13 +413,15 @@ func TestGetFullPath(t *testing.T) {
 		env.On("homeDir", nil).Return("/usr/home")
 		env.On("getcwd", nil).Return(tc.Pwd)
 		env.On("getRuntimeGOOS", nil).Return(tc.GOOS)
+		env.On("stackCount", nil).Return(tc.StackCount)
 		args := &args{
 			PSWD: &tc.Pswd,
 		}
 		env.On("getArgs", nil).Return(args)
 		props := &properties{
 			values: map[Property]interface{}{
-				Style: tc.Style,
+				Style:             tc.Style,
+				StackCountEnabled: tc.StackCountEnabled,
 			},
 		}
 		if tc.FolderSeparatorIcon != "" {