Repository: JanDeDobbeleer/oh-my-posh
Pull request #5132 (2024-06-20 09:59:51)

fix(shell): prioritize `$?` for consistent status in Bash/Zsh
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [x] The commit message follows the [conventional commits][cc] guidelines.
- [ ] Tests for the changes have been added (for bug fixes / features).
- [ ] Docs have been added/updated (for bug fixes / features).

### Description

In Bash (before v5.1) or Zsh, the last error code (`$?`) might not be consistent with the pipeline status. For example, a SIGINT (typically sent by a <kbd>Ctrl</kbd> + <kbd>C</kbd> input) sets `$?` to `130` which means a death by SIGINT, while the previous value of `$PIPESTATUS` (in Bash) or `$pipestatus` (in Zsh) persists. In Bash, this is [a bug which was fixed in v5.1][bash-changelog]. In Zsh, this still happens in the latest version (v5.9).

We can handle such cases consistently. When `$?` is not consistent with the last pipeline status code, we should use the value of `$?` as the argument for both `--status` and `--pipestatus` in an CLI call.

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary
[bash-changelog]: https://git.savannah.gnu.org/cgit/bash.git/tree/CHANGES#n836



Patch:
diff --git a/src/cli/upgrade.go b/src/cli/upgrade.go
index b4820653..8ca0fbe1 100644
--- a/src/cli/upgrade.go
+++ b/src/cli/upgrade.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"runtime"
 
+	"github.com/jandedobbeleer/oh-my-posh/src/engine"
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
 	"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
 	"github.com/spf13/cobra"
@@ -34,8 +35,12 @@ var upgradeCmd = &cobra.Command{
 			return
 		}
 
+		cfg := engine.LoadConfig(env)
+
 		if _, hasNotice := upgrade.Notice(env, true); !hasNotice {
-			fmt.Print("\n✅  no new version available\n\n")
+			if !cfg.DisableNotice {
+				fmt.Print("\n✅  no new version available\n\n")
+			}
 			return
 		}
 
diff --git a/src/segments/path.go b/src/segments/path.go
index bfb161a5..f7a4bf3b 100644
--- a/src/segments/path.go
+++ b/src/segments/path.go
@@ -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 {
@@ -104,7 +109,7 @@ func (pt *Path) Enabled() bool {
 	pt.setStyle()
 	pwd := pt.env.Pwd()
 
-	pt.Location = pt.env.TemplateCache().AbsolutePWD
+	pt.Location = strings.ReplaceAll(pt.env.TemplateCache().AbsolutePWD, `\`, `/`)
 	pt.StackCount = pt.env.StackCount()
 	pt.Writable = pt.env.DirIsWritable(pwd)
 	return true
@@ -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
 	}
diff --git a/src/shell/scripts/omp.bash b/src/shell/scripts/omp.bash
index 259fc7a0..69de98f6 100644
--- a/src/shell/scripts/omp.bash
+++ b/src/shell/scripts/omp.bash
@@ -35,7 +35,7 @@ function _omp_start_timer() {
 }
 
 function _omp_ftcs_command_start() {
-    if [ "::FTCS_MARKS::" == "true" ]; then
+    if [[ "::FTCS_MARKS::" == "true" ]]; then
         printf "\e]133;C\a"
     fi
 }
@@ -55,12 +55,15 @@ function _omp_hook() {
     local omp_elapsed=-1
     local no_exit_code="true"
 
-    if [[ -n "$omp_start_time" ]]; then
+    if [[ "$omp_start_time" ]]; then
         local omp_now=$(::OMP:: get millis --shell=bash)
-        omp_elapsed=$((omp_now-omp_start_time))
+        omp_elapsed=$((omp_now - omp_start_time))
         omp_start_time=""
         no_exit_code="false"
     fi
+    if [[ "${pipeStatus[-1]}" != "$ret" ]]; then
+        pipeStatus=("$ret")
+    fi
 
     set_poshcontext
     _set_posh_cursor_position
@@ -69,11 +72,11 @@ function _omp_hook() {
     return $ret
 }
 
-if [ "$TERM" != "linux" ] && [ -x "$(command -v ::OMP::)" ] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then
+if [[ "$TERM" != "linux" ]] && [[ -x "$(command -v ::OMP::)" ]] && ! [[ "$PROMPT_COMMAND" =~ "_omp_hook" ]]; then
     PROMPT_COMMAND="_omp_hook; $PROMPT_COMMAND"
 fi
 
-if [ "::UPGRADE::" == "true" ]; then
+if [[ "::UPGRADE::" == "true" ]]; then
     echo "::UPGRADENOTICE::"
 fi
 
diff --git a/src/shell/scripts/omp.zsh b/src/shell/scripts/omp.zsh
index 056b65d6..a8fc1f05 100644
--- a/src/shell/scripts/omp.zsh
+++ b/src/shell/scripts/omp.zsh
@@ -13,7 +13,7 @@ function _set_posh_cursor_position() {
   # not supported in Midnight Commander
   # see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3415
   if [[ "::CURSOR::" != "true" ]] || [[ -v MC_SID ]]; then
-      return
+    return
   fi
 
   local oldstty=$(stty -g)
@@ -44,7 +44,8 @@ function prompt_ohmyposh_preexec() {
 }
 
 function prompt_ohmyposh_precmd() {
-  omp_last_error=$? pipeStatus=(${pipestatus[@]})
+  omp_last_error=$?
+  local pipeStatus=(${pipestatus[@]})
   omp_stack_count=${#dirstack[@]}
   omp_elapsed=-1
   no_exit_code="true"
@@ -53,6 +54,9 @@ function prompt_ohmyposh_precmd() {
     omp_elapsed=$(($omp_now-$omp_start_time))
     no_exit_code="false"
   fi
+  if [[ "${pipeStatus[-1]}" != "$omp_last_error" ]]; then
+    pipeStatus=("$omp_last_error")
+  fi
   count=$((POSH_PROMPT_COUNT+1))
   export POSH_PROMPT_COUNT=$count
   set_poshcontext
@@ -67,8 +71,8 @@ add-zsh-hook precmd prompt_ohmyposh_precmd
 add-zsh-hook preexec prompt_ohmyposh_preexec
 
 # perform cleanup so a new initialization in current session works
-if [[ "$(zle -lL self-insert)" = *"_posh-tooltip"* ]]; then
-  zle -N self-insert
+if [[ "$(bindkey " ")" = *"_posh-tooltip"* ]]; then
+  bindkey " " self-insert
 fi
 if [[ "$(zle -lL zle-line-init)" = *"_posh-zle-line-init"* ]]; then
   zle -N zle-line-init
@@ -76,7 +80,7 @@ fi
 
 function _posh-tooltip() {
   # https://github.com/zsh-users/zsh-autosuggestions - clear suggestion to avoid keeping it after the newly inserted space
-  if [[ -n "$(zle -lL autosuggest-clear)" ]]; then
+  if [[ "$(zle -lL autosuggest-clear)" ]]; then
     # only if suggestions not disabled (variable not set)
     if ! [[ -v _ZSH_AUTOSUGGEST_DISABLED ]]; then
       zle autosuggest-clear
@@ -84,7 +88,7 @@ function _posh-tooltip() {
   fi
   zle .self-insert
   # https://github.com/zsh-users/zsh-autosuggestions - fetch new suggestion after the space
-  if [[ -n "$(zle -lL autosuggest-fetch)" ]]; then
+  if [[ "$(zle -lL autosuggest-fetch)" ]]; then
     # only if suggestions not disabled (variable not set)
     if ! [[ -v _ZSH_AUTOSUGGEST_DISABLED ]]; then
       zle autosuggest-fetch
@@ -107,30 +111,30 @@ function _posh-tooltip() {
 }
 
 function _posh-zle-line-init() {
-    [[ $CONTEXT == start ]] || return 0
+  [[ $CONTEXT == start ]] || return 0
 
-    # Start regular line editor
-    (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[1]
-    zle .recursive-edit
-    local -i ret=$?
-    (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[2]
+  # Start regular line editor.
+  (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[1]
+  zle .recursive-edit
+  local -i ret=$?
+  (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[2]
 
-    eval "$(::OMP:: print transient --status="$omp_last_error" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --config="$POSH_THEME" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$no_exit_code")"
-    zle .reset-prompt
+  eval "$(::OMP:: print transient --status="$omp_last_error" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --config="$POSH_THEME" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$no_exit_code")"
+  zle .reset-prompt
 
-    # If we received EOT, we exit the shell
-    if [[ $ret == 0 && $KEYS == $'\4' ]]; then
-        exit
-    fi
+  # Exit the shell if we receive EOT.
+  if [[ $ret == 0 && $KEYS == $'\4' ]]; then
+    exit
+  fi
 
-    # Ctrl-C
-    if (( ret )); then
-        zle .send-break
-    else
-        # Enter
-        zle .accept-line
-    fi
-    return ret
+  if ((ret)); then
+    # TODO (fix): this is not equal to sending a SIGINT, since the status code ($?) is set to 1 instead of 130.
+    zle .send-break
+  else
+    # Enter
+    zle .accept-line
+  fi
+  return ret
 }
 
 function enable_poshtooltips() {
@@ -146,10 +150,10 @@ function enable_poshtransientprompt() {
   bindkey '^[[F' end-of-line
   bindkey '^[[H' beginning-of-line
   _widgets=$(zle -la)
-  if [[ -n "${_widgets[(r)down-line-or-beginning-search]}" ]]; then
+  if [[ "${_widgets[(r)down-line-or-beginning-search]}" ]]; then
     bindkey '^[[B' down-line-or-beginning-search
   fi
-  if [[ -n "${_widgets[(r)up-line-or-beginning-search]}" ]]; then
+  if [[ "${_widgets[(r)up-line-or-beginning-search]}" ]]; then
     bindkey '^[[A' up-line-or-beginning-search
   fi
 }
@@ -163,9 +167,9 @@ if [[ "::TRANSIENT::" = "true" ]]; then
 fi
 
 if [[ "::UPGRADE::" = "true" ]]; then
-    echo "::UPGRADENOTICE::"
+  echo "::UPGRADENOTICE::"
 fi
 
 if [[ "::AUTOUPGRADE::" = "true" ]]; then
-    ::OMP:: upgrade
+  ::OMP:: upgrade
 fi
diff --git a/themes/schema.json b/themes/schema.json
index 6da564e1..af3caee5 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -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
                   }
                 }
               }
diff --git a/website/docs/segments/path.mdx b/website/docs/segments/path.mdx
index 2f29b2ba..3d9a63d2 100644
--- a/website/docs/segments/path.mdx
+++ b/website/docs/segments/path.mdx
@@ -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
 


Test patch:
diff --git a/src/segments/path_test.go b/src/segments/path_test.go
index 55971038..9c70f6ef 100644
--- a/src/segments/path_test.go
+++ b/src/segments/path_test.go
@@ -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)
 	}