Repository: JanDeDobbeleer/oh-my-posh
Pull request #6451 (2025-05-21 09:26:48)

Next
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [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).

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing/git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 03d36564..eb230554 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -115,13 +115,13 @@ the community.
 ## Attribution
 
 This Code of Conduct is adapted from the [Contributor Covenant][homepage],
-version 2.0, available [here][version-2].
+version 2.0, available [in the documentation][version-2].
 
 Community Impact Guidelines were inspired by [Mozilla's code of conduct
 enforcement ladder][moz-div].
 
 For answers to common questions about this code of conduct, see the [FAQ][faq].
-Translations are available [here][translations].
+Translations are available [in the documentation][translations].
 
 [homepage]: https://www.contributor-covenant.org
 [moz-div]: https://github.com/mozilla/diversity
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 97c70dfb..5536c05f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ can be a good starting point.
 
 1. Ensure any dependencies or build artifacts are removed/ignored before creating a commit.
 2. Commits follow the [conventional commits][cc] guidelines.
-(You can [look up the supported *types*][cc-types] along with an explanation [here][cc-types])
+(You can [look up the supported *types*][cc-types] along with an explanation [in the documentation][cc-types])
 3. Update the documentation with details of changes to the functionality, this includes new segments
    or core functionality.
 4. Pull Requests are merged once all checks pass and a project maintainer has approved it.
diff --git a/src/segments/node.go b/src/segments/node.go
index 5641b014..14ef94a8 100644
--- a/src/segments/node.go
+++ b/src/segments/node.go
@@ -71,6 +71,12 @@ func (n *Node) loadContext() {
 			iconProperty: YarnIcon,
 			defaultIcon:  "\U000F011B",
 		},
+		{
+			fileName:     "bun.lockb",
+			name:         "bun",
+			iconProperty: BunIcon,
+			defaultIcon:  "\ue76f",
+		},
 		{
 			fileName:     "package-lock.json",
 			name:         "npm",
@@ -83,12 +89,6 @@ func (n *Node) loadContext() {
 			iconProperty: NPMIcon,
 			defaultIcon:  "\uE71E",
 		},
-		{
-			fileName:     "bun.lockb",
-			name:         "bun",
-			iconProperty: BunIcon,
-			defaultIcon:  "\ue76f",
-		},
 	}
 
 	for _, pm := range packageManagerDefinitions {
diff --git a/src/segments/spotify_linux.go b/src/segments/spotify_linux.go
index c778aa2b..99c19cc7 100644
--- a/src/segments/spotify_linux.go
+++ b/src/segments/spotify_linux.go
@@ -17,7 +17,7 @@ func (s *Spotify) Enabled() bool {
 	// Standard Linux implementation
 	running := s.runLinuxScriptCommand(" string:PlaybackStatus | awk -F '\"' '/string/ {print tolower($2)}'")
 
-	if strings.HasPrefix(running, "Error") {
+	if strings.HasPrefix(running, "Error") || len(running) == 0 {
 		return false
 	}
 
@@ -45,42 +45,37 @@ func (s *Spotify) runLinuxScriptCommand(command string) string {
 	return val
 }
 
-// Implementation for WSL - moved from spotify_wsl.go
 func (s *Spotify) enabledWsl() bool {
-	tlist, err := s.env.RunCommand("tasklist.exe", "/V", "/FI", "Imagename eq Spotify.exe", "/FO", "CSV", "/NH")
-	if err != nil || strings.HasPrefix(tlist, "INFO") {
+	psCommand := `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; (Get-Process Spotify -ErrorAction SilentlyContinue | Where-Object {$_.MainWindowTitle -ne ""} | Select-Object -First 1).MainWindowTitle` //nolint: lll
+
+	windowName, err := s.env.RunCommand("powershell.exe", "-NoProfile", "-NonInteractive", "-Command", psCommand)
+	if err != nil {
+		s.Status = stopped
 		return false
 	}
 
-	records := strings.Split(tlist, "\n")
-	if len(records) == 0 {
+	title := strings.TrimSpace(windowName)
+	if len(title) == 0 || !strings.Contains(title, " - ") {
+		s.Status = stopped
 		return false
 	}
 
-	for _, record := range records {
-		record = strings.TrimSpace(record)
-		fields := strings.Split(record, ",")
-		if len(fields) == 0 {
-			continue
-		}
-
-		// last elemant is the title
-		title := fields[len(fields)-1]
-		// trim leading and trailing quotes from the field
-		title = strings.TrimPrefix(title, `"`)
-		title = strings.TrimSuffix(title, `"`)
-		if !strings.Contains(title, " - ") {
-			continue
-		}
-
-		infos := strings.Split(title, " - ")
-		s.Artist = infos[0]
-		s.Track = strings.Join(infos[1:], " - ")
-		s.Status = playing
-		s.resolveIcon()
-
-		return true
+	infos := strings.SplitN(title, " - ", 2)
+	if len(infos) < 2 {
+		s.Status = stopped
+		return false
 	}
 
-	return false
+	s.Artist = strings.TrimSpace(infos[0])
+	s.Track = strings.TrimSpace(infos[1])
+
+	if len(s.Artist) == 0 || len(s.Track) == 0 {
+		s.Status = stopped
+		return false
+	}
+
+	s.Status = playing
+	s.resolveIcon()
+
+	return true
 }


Test patch:
diff --git a/src/segments/spotify_linux_test.go b/src/segments/spotify_linux_test.go
index d8b59bcd..65cebc8d 100644
--- a/src/segments/spotify_linux_test.go
+++ b/src/segments/spotify_linux_test.go
@@ -15,35 +15,66 @@ import (
 
 func TestSpotifyLinux(t *testing.T) {
 	cases := []struct {
-		Error    error
-		Expected string
-		Status   string
-		Artist   string
-		Track    string
-		Running  bool
+		Case            string
+		Status          string
+		Artist          string
+		Track           string
+		Expected        string
+		ExpectedEnabled bool
 	}{
-		{Running: false, Expected: ""},
-		{Running: false, Expected: "", Error: errors.New("oops")},
-		{Running: true, Expected: "\uF8E3 Candlemass - Spellbreaker", Status: "paused", Artist: "Candlemass", Track: "Spellbreaker"},
-		{Running: true, Expected: "\uE602 Candlemass - Spellbreaker", Status: "playing", Artist: "Candlemass", Track: "Spellbreaker"},
+		{Case: "no data", ExpectedEnabled: false},
+		{Case: "error", ExpectedEnabled: false, Status: "Error.ServiceUnknown"},
+		{Case: "paused", ExpectedEnabled: true, Expected: "\uF8E3 Candlemass - Spellbreaker", Status: "paused", Artist: "Candlemass", Track: "Spellbreaker"},
+		{Case: "playing", ExpectedEnabled: true, Expected: "\uE602 Candlemass - Spellbreaker", Status: "playing", Artist: "Candlemass", Track: "Spellbreaker"},
 	}
 	for _, tc := range cases {
 		env := new(mock.Environment)
 		env.On("IsWsl").Return(false)
 
 		dbusCMD := "dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.Get string:org.mpris.MediaPlayer2.Player"
-		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:PlaybackStatus | awk -F '\"' '/string/ {print tolower($2)}'").Return(tc.Status, nil)
-		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:Metadata | awk -F '\"' 'BEGIN {RS=\"entry\"}; /'xesam:artist'/ {a=$4} END {print a}'").Return(tc.Artist, nil)
-		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:Metadata | awk -F '\"' 'BEGIN {RS=\"entry\"}; /'xesam:title'/ {t=$4} END {print t}'").Return(tc.Track, nil)
+		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:PlaybackStatus | awk -F '\"' '/string/ {print tolower($2)}'").Return(tc.Status)
+		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:Metadata | awk -F '\"' 'BEGIN {RS=\"entry\"}; /'xesam:artist'/ {a=$4} END {print a}'").Return(tc.Artist)
+		env.On("RunShellCommand", shell.BASH, dbusCMD+" string:Metadata | awk -F '\"' 'BEGIN {RS=\"entry\"}; /'xesam:title'/ {t=$4} END {print t}'").Return(tc.Track)
 
 		s := &Spotify{}
 		s.Init(properties.Map{}, env)
 
-		enable := s.Enabled()
-		assert.True(t, enable)
-		if tc.Running {
-			assert.True(t, s.Enabled())
-			assert.Equal(t, tc.Expected, renderTemplate(env, s.Template(), s))
+		got := s.Enabled()
+
+		assert.Equal(t, tc.ExpectedEnabled, got, tc.Case)
+		if tc.ExpectedEnabled {
+			assert.Equal(t, tc.Expected, renderTemplate(env, s.Template(), s), tc.Case)
+		}
+	}
+}
+
+func TestSpotifyWSL(t *testing.T) {
+	cases := []struct {
+		Case            string
+		Error           error
+		Title           string
+		Expected        string
+		ExpectedEnabled bool
+	}{
+		{Case: "nothing"},
+		{Case: "error", Error: errors.New("oops")},
+		{Case: "title", ExpectedEnabled: true, Expected: "\ue602 Xzibit - Crash (ft. Royce Da 5'9\" K.A.A.N.)", Title: `Xzibit - Crash (ft. Royce Da 5'9" K.A.A.N.)`},
+	}
+	for _, tc := range cases {
+		env := new(mock.Environment)
+		env.On("IsWsl").Return(true)
+
+		psCommand := `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; (Get-Process Spotify -ErrorAction SilentlyContinue | Where-Object {$_.MainWindowTitle -ne ""} | Select-Object -First 1).MainWindowTitle` //nolint: lll
+		env.On("RunCommand", "powershell.exe", []string{"-NoProfile", "-NonInteractive", "-Command", psCommand}).Return(tc.Title, tc.Error)
+
+		s := &Spotify{}
+		s.Init(properties.Map{}, env)
+
+		got := s.Enabled()
+
+		assert.Equal(t, tc.ExpectedEnabled, got, tc.Case)
+		if tc.ExpectedEnabled {
+			assert.Equal(t, tc.Expected, renderTemplate(env, s.Template(), s), tc.Case)
 		}
 	}
 }
diff --git a/src/segments/spotify_wsl_test.go b/src/segments/spotify_wsl_test.go
deleted file mode 100644
index 1b17d84a..00000000
--- a/src/segments/spotify_wsl_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-//go:build !darwin && !windows
-
-package segments
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/jandedobbeleer/oh-my-posh/src/properties"
-	"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestSpotifyWsl(t *testing.T) {
-	cases := []struct {
-		Case            string
-		ExpectedString  string
-		ExecOutput      string
-		ExpectedEnabled bool
-	}{
-		{
-			Case:            "Spotify not running",
-			ExpectedString:  "-",
-			ExpectedEnabled: false,
-			ExecOutput:      "INFO: No tasks are running which match the specified criteria.\n",
-		},
-		{
-			Case:            "Spotify stopped/paused",
-			ExpectedString:  "-",
-			ExpectedEnabled: false,
-			ExecOutput: `"Spotify.exe","21824","Console","1","124,928 K","Running","PC\user","0:09:44","Spotify Premium"
-"Spotify.exe","21876","Console","1","25,520 K","Running","PC\user","0:00:00","N/A"
-"Spotify.exe","21988","Console","1","60,840 K","Not Responding","PC\user","0:04:56","AngleHiddenWindow"
-"Spotify.exe","22052","Console","1","29,040 K","Unknown","PC\user","0:00:00","N/A"
-"Spotify.exe","22072","Console","1","43,960 K","Unknown","PC\user","0:01:50","N/A"
-"Spotify.exe","10404","Console","1","256,924 K","Unknown","PC\user","0:10:49","N/A"`,
-		},
-		{
-			Case:            "Spotify playing",
-			ExpectedString:  "\ue602 Candlemass - Spellbreaker",
-			ExpectedEnabled: true,
-			ExecOutput: `"Spotify.exe","21824","Console","1","124,928 K","Running","PC\user","0:09:44","Candlemass - Spellbreaker"
-"Spotify.exe","21876","Console","1","25,520 K","Running","PC\user","0:00:00","N/A"
-"Spotify.exe","21988","Console","1","60,840 K","Not Responding","PC\user","0:04:56","AngleHiddenWindow"
-"Spotify.exe","22052","Console","1","29,040 K","Unknown","PC\user","0:00:00","N/A"
-"Spotify.exe","22072","Console","1","43,960 K","Unknown","PC\user","0:01:50","N/A"
-"Spotify.exe","10404","Console","1","256,924 K","Unknown","PC\user","0:10:49","N/A"`,
-		},
-		{
-			Case:            "Spotify playing",
-			ExpectedString:  "\ue602 Grabbitz - Another Form Of \"Goodbye\"",
-			ExpectedEnabled: true,
-			ExecOutput: `"Spotify.exe","13748","Console","1","303.744 K","Running","GARMIN\elderbroekowe","0:03:58","Grabbitz - Another Form Of "Goodbye""
-"Spotify.exe","4208","Console","1","31.544 K","Running","GARMIN\elderbroekowe","0:00:00","N/A"
-"Spotify.exe","14528","Console","1","184.020 K","Running","GARMIN\elderbroekowe","0:02:54","N/A"
-"Spotify.exe","14488","Console","1","53.828 K","Unknown","GARMIN\elderbroekowe","0:00:08","N/A"
-"Spotify.exe","14800","Console","1","29.576 K","Unknown","GARMIN\elderbroekowe","0:00:00","N/A"
-"Spotify.exe","19836","Console","1","237.360 K","Unknown","GARMIN\elderbroekowe","0:07:46","N/A"`,
-		},
-		{
-			Case:            "tasklist.exe not in path",
-			ExpectedString:  "-",
-			ExpectedEnabled: false,
-			ExecOutput:      "",
-		},
-	}
-	for _, tc := range cases {
-		env := new(mock.Environment)
-		env.On("IsWsl").Return(true)
-		env.On("RunCommand", "tasklist.exe", []string{"/V", "/FI", "Imagename eq Spotify.exe", "/FO", "CSV", "/NH"}).Return(tc.ExecOutput, nil)
-
-		s := &Spotify{}
-		s.Init(properties.Map{}, env)
-
-		assert.Equal(t, tc.ExpectedEnabled, s.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
-		assert.Equal(t, tc.ExpectedString, renderTemplate(env, s.Template(), s), fmt.Sprintf("Failed in case: %s", tc.Case))
-	}
-}