Repository: JanDeDobbeleer/oh-my-posh
Pull request #3338 (2023-01-09 07:34:33)

monday schmonday
### 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)

### Description

- fix(fish): remove hyperlink escaping
- fix(pwd): do not print quotes
- docs(error): you can't use cross segment properties
- feat(git): add commit information
- fix(spotify): parse information correctly for PWA on Windows
- feat: add elixir segment

<!---

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/src/ansi/ansi_writer.go b/src/ansi/ansi_writer.go
index c8f99e37..c33e3208 100644
--- a/src/ansi/ansi_writer.go
+++ b/src/ansi/ansi_writer.go
@@ -134,8 +134,8 @@ func (w *Writer) Init(shellName string) {
 		w.escapeRight = "\\]"
 		w.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]"
 		w.hyperlinkRegex = `(?P<STR>\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P<TEXT>.+)\\\[\x1b\]8;;\x1b\\\\\\\])`
-		w.osc99 = "\\[\x1b]9;9;\"%s\"\x1b\\\\\\]"
-		w.osc7 = "\\[\x1b]7;\"file://%s/%s\"\x1b\\\\\\]"
+		w.osc99 = "\\[\x1b]9;9;%s\x1b\\\\\\]"
+		w.osc7 = "\\[\x1b]7;file://%s/%s\x1b\\\\\\]"
 		w.osc51 = "\\[\x1b]51;A;%s@%s:%s\x1b\\\\\\]"
 	case "zsh":
 		w.format = "%%{%s%%}"
@@ -151,8 +151,8 @@ func (w *Writer) Init(shellName string) {
 		w.escapeRight = "%}"
 		w.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}"
 		w.hyperlinkRegex = `(?P<STR>%{\x1b]8;;(.+)\x1b\\%}(?P<TEXT>.+)%{\x1b]8;;\x1b\\%})`
-		w.osc99 = "%%{\x1b]9;9;\"%s\"\x1b\\%%}"
-		w.osc7 = "%%{\x1b]7;file:\"//%s/%s\"\x1b\\%%}"
+		w.osc99 = "%%{\x1b]9;9;%s\x1b\\%%}"
+		w.osc7 = "%%{\x1b]7;file://%s/%s\x1b\\%%}"
 		w.osc51 = "%%{\x1b]51;A%s@%s:%s\x1b\\%%}"
 	default:
 		w.linechange = "\x1b[%d%s"
@@ -168,8 +168,8 @@ func (w *Writer) Init(shellName string) {
 		// https://github.com/JanDeDobbeleer/oh-my-posh/pull/3288#issuecomment-1369137068
 		w.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\"
 		w.hyperlinkRegex = "(?P<STR>\x1b]8;;(.+)\x1b\\\\\\\\?(?P<TEXT>.+)\x1b]8;;\x1b\\\\)"
-		w.osc99 = "\x1b]9;9;\"%s\"\x1b\\"
-		w.osc7 = "\x1b]7;\"file://%s/%s\"\x1b\\"
+		w.osc99 = "\x1b]9;9;%s\x1b\\"
+		w.osc7 = "\x1b]7;file://%s/%s\x1b\\"
 		w.osc51 = "\x1b]51;A%s@%s:%s\x1b\\"
 	}
 }
diff --git a/src/ansi/ansi_writer_hyperlink.go b/src/ansi/ansi_writer_hyperlink.go
index a826fda5..c119edf7 100644
--- a/src/ansi/ansi_writer_hyperlink.go
+++ b/src/ansi/ansi_writer_hyperlink.go
@@ -5,7 +5,6 @@ import (
 	"strings"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/regex"
-	"github.com/jandedobbeleer/oh-my-posh/src/shell"
 	"github.com/mattn/go-runewidth"
 )
 
@@ -69,61 +68,17 @@ func (w *Writer) replaceHyperlink(text string) string {
 		return text
 	}
 
+	linkText := results["TEXT"]
+
 	// we only care about the length of the text part
-	w.length += runewidth.StringWidth(results["TEXT"])
+	w.length += runewidth.StringWidth(linkText)
 
 	if w.Plain {
-		return results["TEXT"]
+		return linkText
 	}
 
-	linkText := w.escapeLinkTextForFishShell(results["TEXT"])
 	// build hyperlink ansi
 	hyperlink := fmt.Sprintf(w.hyperlink, results["URL"], linkText)
 	// replace original text by the new ones
 	return strings.Replace(text, results["ALL"], hyperlink, 1)
 }
-
-func (w *Writer) escapeLinkTextForFishShell(text string) string {
-	if w.shell != shell.FISH {
-		return text
-	}
-	escapeChars := map[string]string{
-		`c`: `\c`,
-		`a`: `\a`,
-		`b`: `\b`,
-		`e`: `\e`,
-		`f`: `\f`,
-		`n`: `\n`,
-		`r`: `\r`,
-		`t`: `\t`,
-		`v`: `\v`,
-		`$`: `\$`,
-		`*`: `\*`,
-		`?`: `\?`,
-		`~`: `\~`,
-		`%`: `\%`,
-		`#`: `\#`,
-		`(`: `\(`,
-		`)`: `\)`,
-		`{`: `\{`,
-		`}`: `\}`,
-		`[`: `\[`,
-		`]`: `\]`,
-		`<`: `\<`,
-		`>`: `\>`,
-		`^`: `\^`,
-		`&`: `\&`,
-		`;`: `\;`,
-		`"`: `\"`,
-		`'`: `\'`,
-		`x`: `\x`,
-		`X`: `\X`,
-		`0`: `\0`,
-		`u`: `\u`,
-		`U`: `\U`,
-	}
-	if val, ok := escapeChars[text[0:1]]; ok {
-		return val + text[1:]
-	}
-	return text
-}
diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go
index 316ed4de..14d5abe3 100644
--- a/src/cli/config_export_image.go
+++ b/src/cli/config_export_image.go
@@ -57,6 +57,11 @@ Exports the config to an image file using customized output options.`,
 		env.Init()
 		defer env.Close()
 		cfg := engine.LoadConfig(env)
+
+		// set dsane defaults for things we don't print
+		cfg.ConsoleTitleTemplate = ""
+		cfg.PWD = ""
+
 		writerColors := cfg.MakeColors()
 		writer := &ansi.Writer{
 			TerminalBackground: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground),
@@ -68,7 +73,9 @@ Exports the config to an image file using customized output options.`,
 			Env:    env,
 			Writer: writer,
 		}
+
 		prompt := eng.PrintPrimary()
+
 		imageCreator := &engine.ImageRenderer{
 			AnsiString:    prompt,
 			Author:        author,
@@ -82,6 +89,7 @@ Exports the config to an image file using customized output options.`,
 		}
 		imageCreator.Init(env.Flags().Config)
 		err := imageCreator.SavePNG()
+
 		if err != nil {
 			fmt.Print(err.Error())
 		}
diff --git a/src/engine/image.go b/src/engine/image.go
index 4a26187a..0d97ee74 100644
--- a/src/engine/image.go
+++ b/src/engine/image.go
@@ -69,9 +69,6 @@ const (
 	strikethroughReset  = "strikethroughr"
 	color16             = "color16"
 	left                = "left"
-	osc99               = "osc99"
-	osc7                = "osc7"
-	osc51               = "osc51"
 	lineChange          = "linechange"
 	consoleTitle        = "title"
 	link                = "link"
@@ -197,9 +194,6 @@ func (ir *ImageRenderer) Init(config string) {
 		strikethroughReset:  `^(?P<STR>\x1b\[29m)`,
 		color16:             `^(?P<STR>\x1b\[(?P<BC>[349][0-7]|10[0-7]|39)m)`,
 		left:                `^(?P<STR>\x1b\[(\d{1,3})D)`,
-		osc99:               `^(?P<STR>\x1b\]9;9;(.+)\x1b\\)`,
-		osc7:                `^(?P<STR>\x1b\]7;(.+)\x1b\\)`,
-		osc51:               `^(?P<STR>\x1b\]51;A(.+)\x1b\\)`,
 		lineChange:          `^(?P<STR>\x1b\[(\d)[FB])`,
 		consoleTitle:        `^(?P<STR>\x1b\]0;(.+)\007)`,
 		link:                fmt.Sprintf(`^%s`, regex.LINK),
@@ -507,7 +501,7 @@ func (ir *ImageRenderer) shouldPrint() bool {
 		case boldReset, italicReset, underlineReset, overlineReset:
 			ir.style = ""
 			return false
-		case strikethrough, strikethroughReset, left, osc99, osc7, osc51, lineChange, consoleTitle:
+		case strikethrough, strikethroughReset, left, lineChange, consoleTitle:
 			return false
 		case color16:
 			ir.setBase16Color(match[bc])
diff --git a/src/engine/segment.go b/src/engine/segment.go
index 8410a5ec..629ec861 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -126,6 +126,8 @@ const (
 	DENO SegmentType = "deno"
 	// DOTNET writes which dotnet version is currently active
 	DOTNET SegmentType = "dotnet"
+	// ELIXIR writes the elixir version
+	ELIXIR SegmentType = "elixir"
 	// EXECUTIONTIME writes the execution time of the last run command
 	EXECUTIONTIME SegmentType = "executiontime"
 	// EXIT writes the last exit code
@@ -214,6 +216,8 @@ const (
 	TIME SegmentType = "time"
 	// UI5 Tooling segment
 	UI5TOOLING SegmentType = "ui5tooling"
+	// VALA writes the active vala version
+	VALA SegmentType = "vala"
 	// WAKATIME writes tracked time spend in dev editors
 	WAKATIME SegmentType = "wakatime"
 	// WINREG queries the Windows registry.
@@ -246,6 +250,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	DENO:          func() SegmentWriter { return &segments.Deno{} },
 	DOTNET:        func() SegmentWriter { return &segments.Dotnet{} },
 	EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} },
+	ELIXIR:        func() SegmentWriter { return &segments.Elixir{} },
 	EXIT:          func() SegmentWriter { return &segments.Exit{} },
 	FLUTTER:       func() SegmentWriter { return &segments.Flutter{} },
 	FOSSIL:        func() SegmentWriter { return &segments.Fossil{} },
@@ -289,6 +294,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	TEXT:          func() SegmentWriter { return &segments.Text{} },
 	TIME:          func() SegmentWriter { return &segments.Time{} },
 	UI5TOOLING:    func() SegmentWriter { return &segments.UI5Tooling{} },
+	VALA:          func() SegmentWriter { return &segments.Vala{} },
 	WAKATIME:      func() SegmentWriter { return &segments.Wakatime{} },
 	WINREG:        func() SegmentWriter { return &segments.WindowsRegistry{} },
 	WITHINGS:      func() SegmentWriter { return &segments.Withings{} },
diff --git a/src/segments/elixir.go b/src/segments/elixir.go
new file mode 100644
index 00000000..05b57a54
--- /dev/null
+++ b/src/segments/elixir.go
@@ -0,0 +1,34 @@
+package segments
+
+import (
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+type Elixir struct {
+	language
+}
+
+func (e *Elixir) Template() string {
+	return languageTemplate
+}
+
+func (e *Elixir) Init(props properties.Properties, env platform.Environment) {
+	e.language = language{
+		env:        env,
+		props:      props,
+		extensions: []string{"*.ex"},
+		commands: []*cmd{
+			{
+				executable: "elixir",
+				args:       []string{"--version"},
+				regex:      `Elixir (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)))`,
+			},
+		},
+		versionURLTemplate: "https://github.com/elixir-lang/elixir/releases/tag/v{{ .Full }}",
+	}
+}
+
+func (e *Elixir) Enabled() bool {
+	return e.language.Enabled()
+}
diff --git a/src/segments/git.go b/src/segments/git.go
index 1149f523..cc13b716 100644
--- a/src/segments/git.go
+++ b/src/segments/git.go
@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
 	"github.com/jandedobbeleer/oh-my-posh/src/properties"
@@ -14,6 +15,19 @@ import (
 	"gopkg.in/ini.v1"
 )
 
+type Commit struct {
+	// git log -1 --pretty="format:%an%n%ae%n%cn%n%ce%n%at%n%s"
+	Author    *User
+	Committer *User
+	Subject   string
+	Timestamp time.Time
+}
+
+type User struct {
+	Name  string
+	Email string
+}
+
 // GitStatus represents part of the status of a git repository
 type GitStatus struct {
 	ScmStatus
@@ -119,6 +133,8 @@ type Git struct {
 	poshgit       bool
 	stashCount    int
 	worktreeCount int
+
+	commit *Commit
 }
 
 func (g *Git) Template() string {
@@ -160,6 +176,43 @@ func (g *Git) Enabled() bool {
 	return true
 }
 
+func (g *Git) Commit() *Commit {
+	if g.commit != nil {
+		return g.commit
+	}
+	g.commit = &Commit{
+		Author:    &User{},
+		Committer: &User{},
+	}
+	commitBody := g.getGitCommandOutput("log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s")
+	splitted := strings.Split(strings.TrimSpace(commitBody), "\n")
+	for _, line := range splitted {
+		line = strings.TrimSpace(line)
+		if len(line) <= 3 {
+			continue
+		}
+		anchor := line[:3]
+		line = line[3:]
+		switch anchor {
+		case "an:":
+			g.commit.Author.Name = line
+		case "ae:":
+			g.commit.Author.Email = line
+		case "cn:":
+			g.commit.Committer.Name = line
+		case "ce:":
+			g.commit.Committer.Email = line
+		case "at:":
+			if t, err := strconv.ParseInt(line, 10, 64); err == nil {
+				g.commit.Timestamp = time.Unix(t, 0)
+			}
+		case "su:":
+			g.commit.Subject = line
+		}
+	}
+	return g.commit
+}
+
 func (g *Git) StashCount() int {
 	if g.poshgit || g.stashCount != 0 {
 		return g.stashCount
diff --git a/src/segments/spotify_windows.go b/src/segments/spotify_windows.go
index 443bcaad..bdf7f458 100644
--- a/src/segments/spotify_windows.go
+++ b/src/segments/spotify_windows.go
@@ -11,30 +11,43 @@ func (s *Spotify) Enabled() bool {
 	// Can be either "Spotify xxx" or the song name "Candlemass - Spellbreaker"
 	windowTitle, err := s.env.QueryWindowTitles("spotify.exe", `^(Spotify.*)|(.*\s-\s.*)$`)
 	if err == nil {
-		return s.parseSpotifyTitle(windowTitle, " - ")
+		return s.parseNativeTitle(windowTitle)
 	}
 	windowTitle, err = s.env.QueryWindowTitles("msedge.exe", `^(Spotify.*)`)
 	if err != nil {
 		return false
 	}
-	return s.parseWebSpotifyTitle(windowTitle)
+	return s.parseWebTitle(windowTitle)
 }
 
-func (s *Spotify) parseWebSpotifyTitle(windowTitle string) bool {
-	windowTitle = strings.TrimPrefix(windowTitle, "Spotify - ")
-	return s.parseSpotifyTitle(windowTitle, " • ")
+func (s *Spotify) parseNativeTitle(windowTitle string) bool {
+	separator := " - "
+
+	if !strings.Contains(windowTitle, separator) {
+		s.Status = stopped
+		return false
+	}
+
+	index := strings.Index(windowTitle, separator)
+	s.Artist = windowTitle[0:index]
+	s.Track = windowTitle[index+len(separator):]
+	s.Status = playing
+	s.resolveIcon()
+	return true
 }
 
-func (s *Spotify) parseSpotifyTitle(windowTitle, separator string) bool {
+func (s *Spotify) parseWebTitle(windowTitle string) bool {
+	windowTitle = strings.TrimPrefix(windowTitle, "Spotify - ")
+	separator := " • "
+
 	if !strings.Contains(windowTitle, separator) {
 		s.Status = stopped
 		return false
 	}
 
-	infos := strings.Split(windowTitle, separator)
-	s.Artist = infos[0]
-	// remove first element and concat others(a song can contains also a " - ")
-	s.Track = strings.Join(infos[1:], separator)
+	index := strings.Index(windowTitle, separator)
+	s.Track = windowTitle[0:index]
+	s.Artist = windowTitle[index+len(separator):]
 	s.Status = playing
 	s.resolveIcon()
 	return true
diff --git a/src/segments/vala.go b/src/segments/vala.go
new file mode 100644
index 00000000..40517967
--- /dev/null
+++ b/src/segments/vala.go
@@ -0,0 +1,34 @@
+package segments
+
+import (
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+)
+
+type Vala struct {
+	language
+}
+
+func (v *Vala) Template() string {
+	return languageTemplate
+}
+
+func (v *Vala) Init(props properties.Properties, env platform.Environment) {
+	v.language = language{
+		env:        env,
+		props:      props,
+		extensions: []string{"*.vala"},
+		commands: []*cmd{
+			{
+				executable: "vala",
+				args:       []string{"--version"},
+				regex:      `Vala (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)))`,
+			},
+		},
+		versionURLTemplate: "https://gitlab.gnome.org/GNOME/vala/raw/{{ .Major }}.{{ .Minor }}/NEWS",
+	}
+}
+
+func (v *Vala) Enabled() bool {
+	return v.language.Enabled()
+}
diff --git a/themes/schema.json b/themes/schema.json
index 6ba544d3..45e18399 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -2028,6 +2028,37 @@
             }
           }
         },
+        {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "elixir"
+              }
+            }
+          },
+          "then": {
+            "title": "Elixir Segment",
+            "description": "https://ohmyposh.dev/docs/segments/elixir",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "home_enabled": {
+                    "$ref": "#/definitions/home_enabled"
+                  },
+                  "fetch_version": {
+                    "$ref": "#/definitions/fetch_version"
+                  },
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  },
+                  "missing_command_text": {
+                    "$ref": "#/definitions/missing_command_text"
+                  }
+                }
+              }
+            }
+          }
+        },
         {
           "if": {
             "properties": {
@@ -2641,6 +2672,37 @@
             }
           }
         },
+        {
+          "if": {
+            "properties": {
+              "type": {
+                "const": "vala"
+              }
+            }
+          },
+          "then": {
+            "title": "Vala Segment",
+            "description": "https://ohmyposh.dev/docs/segments/vala",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "home_enabled": {
+                    "$ref": "#/definitions/home_enabled"
+                  },
+                  "fetch_version": {
+                    "$ref": "#/definitions/fetch_version"
+                  },
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  },
+                  "missing_command_text": {
+                    "$ref": "#/definitions/missing_command_text"
+                  }
+                }
+              }
+            }
+          }
+        },
         {
           "if": {
             "properties": {
diff --git a/website/docs/configuration/line-error.mdx b/website/docs/configuration/line-error.mdx
index 7cb2722c..c13a8061 100644
--- a/website/docs/configuration/line-error.mdx
+++ b/website/docs/configuration/line-error.mdx
@@ -20,8 +20,6 @@ There are two config settings you need to tweak:
 - `error_line`: displays when the line is faulty
 
 You can use go [text/template][go-text-template] templates extended with [sprig][sprig] to enrich the text.
-All [template][templates] functionality is available, even reusing [cross segment template properties][cstp] from
-the previous primary prompt run.
 
 ## Configuration
 
diff --git a/website/docs/segments/elixir.mdx b/website/docs/segments/elixir.mdx
new file mode 100644
index 00000000..0828a73f
--- /dev/null
+++ b/website/docs/segments/elixir.mdx
@@ -0,0 +1,55 @@
+---
+id: elixir
+title: Elixir
+sidebar_label: Elixir
+---
+
+## What
+
+Display the currently active elixir version.
+
+## Sample Configuration
+
+```json
+{
+  "type": "elixir",
+  "style": "powerline",
+  "powerline_symbol": "\uE0B0",
+  "foreground": "#ffffff",
+  "background": "#422251",
+  "template": " \ue62d {{ .Full }} "
+}
+```
+
+## Properties
+
+| Name                   | Type      | Description                                                                                                                                            |
+| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `home_enabled`         | `boolean` | display the segment in the HOME folder or not - defaults to `false`                                                                                    |
+| `fetch_version`        | `boolean` | fetch the flutter version - defaults to `true`                                                                                                         |
+| `missing_command_text` | `string`  | text to display when the command is missing - defaults to empty                                                                                        |
+| `display_mode`         | `string`  | <ul><li>`always`: the segment is always displayed</li><li>`files`: the segment is only displayed when `*.ex` files are present (**default**)</li></ul> |
+| `version_url_template` | `string`  | a go [text/template][go-text-template] [template][templates] that creates the URL of the version info / release notes                                  |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }}
+```
+
+:::
+
+### Properties
+
+| Name     | Type     | Description                                        |
+| -------- | -------- | -------------------------------------------------- |
+| `.Full`  | `string` | the full version                                   |
+| `.Major` | `string` | major number                                       |
+| `.Minor` | `string` | minor number                                       |
+| `.URL`   | `string` | URL of the version info / release notes            |
+| `.Error` | `string` | error encountered when fetching the version string |
+
+[go-text-template]: https://golang.org/pkg/text/template/
+[templates]: /docs/configuration/templates
diff --git a/website/docs/segments/git.mdx b/website/docs/segments/git.mdx
index 85e4bf56..eaead6c1 100644
--- a/website/docs/segments/git.mdx
+++ b/website/docs/segments/git.mdx
@@ -145,6 +145,7 @@ You can set the following properties to `true` to enable fetching additional inf
 | `.IsBare`        | `boolean`   | if in a bare repo or not, only set when `fetch_bare_info` is set to `true`                                                       |
 | `.Dir`           | `string`    | the repository's root directory                                                                                                  |
 | `.Kraken`        | `string`    | a link to the current HEAD in [GitKraken][kraken-ref] for use in [hyperlinks][hyperlinks] in templates `{{ url .HEAD .Kraken }}` |
+| `.Commit`        | `Commit`    | HEAD commit information (see below)                                                                                              |
 
 ### GitStatus
 
@@ -158,6 +159,22 @@ You can set the following properties to `true` to enable fetching additional inf
 | `.Changed`   | `boolean` | if the status contains changes or not        |
 | `.String`    | `string`  | a string representation of the changes above |
 
+### Commit
+
+| Name         | Type        | Description                            |
+| ------------ | ----------- | -------------------------------------- |
+| `.Author`    | `User`      | the author or the commit (see below)   |
+| `.Committer` | `User`      | the comitter or the commit (see below) |
+| `.Subject`   | `string`    | the commit subject                     |
+| `.Timestamp` | `time.Time` | the commit timestamp                   |
+
+### User
+
+| Name     | Type     | Description      |
+| -------- | -------- | ---------------- |
+| `.Name`  | `string` | the user's name  |
+| `.Email` | `string` | the user's email |
+
 Local changes use the following syntax:
 
 | Icon | Description |
diff --git a/website/docs/segments/vala.mdx b/website/docs/segments/vala.mdx
new file mode 100644
index 00000000..760c3215
--- /dev/null
+++ b/website/docs/segments/vala.mdx
@@ -0,0 +1,55 @@
+---
+id: vala
+title: Vala
+sidebar_label: Vala
+---
+
+## What
+
+Display the currently active vala version.
+
+## Sample Configuration
+
+```json
+{
+  "type": "vala",
+  "style": "powerline",
+  "powerline_symbol": "\uE0B0",
+  "foreground": "#ffffff",
+  "background": "#5E20A4",
+  "template": " \ue69e {{ .Full }} "
+}
+```
+
+## Properties
+
+| Name                   | Type      | Description                                                                                                                                              |
+| ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `home_enabled`         | `boolean` | display the segment in the HOME folder or not - defaults to `false`                                                                                      |
+| `fetch_version`        | `boolean` | fetch the flutter version - defaults to `true`                                                                                                           |
+| `missing_command_text` | `string`  | text to display when the command is missing - defaults to empty                                                                                          |
+| `display_mode`         | `string`  | <ul><li>`always`: the segment is always displayed</li><li>`files`: the segment is only displayed when `*.vala` files are present (**default**)</li></ul> |
+| `version_url_template` | `string`  | a go [text/template][go-text-template] [template][templates] that creates the URL of the version info / release notes                                    |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }}
+```
+
+:::
+
+### Properties
+
+| Name     | Type     | Description                                        |
+| -------- | -------- | -------------------------------------------------- |
+| `.Full`  | `string` | the full version                                   |
+| `.Major` | `string` | major number                                       |
+| `.Minor` | `string` | minor number                                       |
+| `.URL`   | `string` | URL of the version info / release notes            |
+| `.Error` | `string` | error encountered when fetching the version string |
+
+[go-text-template]: https://golang.org/pkg/text/template/
+[templates]: /docs/configuration/templates
diff --git a/website/sidebars.js b/website/sidebars.js
index 665a616f..1b3ce469 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -67,6 +67,7 @@ module.exports = {
         "segments/dart",
         "segments/deno",
         "segments/dotnet",
+        "segments/elixir",
         "segments/executiontime",
         "segments/exit",
         "segments/flutter",
@@ -111,6 +112,7 @@ module.exports = {
         "segments/text",
         "segments/time",
         "segments/ui5tooling",
+        "segments/vala",
         "segments/wakatime",
         "segments/withings",
         "segments/winreg",


Test patch:
diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go
index 0ec1e065..9d9d31f9 100644
--- a/src/engine/engine_test.go
+++ b/src/engine/engine_test.go
@@ -52,12 +52,12 @@ func TestPrintPWD(t *testing.T) {
 		OSC99    bool
 	}{
 		{Case: "Empty PWD"},
-		{Case: "OSC99", PWD: ansi.OSC99, Expected: "\x1b]9;9;\"pwd\"\x1b\\"},
-		{Case: "OSC7", PWD: ansi.OSC7, Expected: "\x1b]7;\"file://host/pwd\"\x1b\\"},
+		{Case: "OSC99", PWD: ansi.OSC99, Expected: "\x1b]9;9;pwd\x1b\\"},
+		{Case: "OSC7", PWD: ansi.OSC7, Expected: "\x1b]7;file://host/pwd\x1b\\"},
 		{Case: "OSC51", PWD: ansi.OSC51, Expected: "\x1b]51;Auser@host:pwd\x1b\\"},
-		{Case: "Deprecated OSC99", OSC99: true, Expected: "\x1b]9;9;\"pwd\"\x1b\\"},
+		{Case: "Deprecated OSC99", OSC99: true, Expected: "\x1b]9;9;pwd\x1b\\"},
 		{Case: "Template (empty)", PWD: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"},
-		{Case: "Template (non empty)", PWD: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;\"file://host/pwd\"\x1b\\"},
+		{Case: "Template (non empty)", PWD: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;file://host/pwd\x1b\\"},
 	}
 
 	for _, tc := range cases {
diff --git a/src/segments/elixir_test.go b/src/segments/elixir_test.go
new file mode 100644
index 00000000..9fe7bf89
--- /dev/null
+++ b/src/segments/elixir_test.go
@@ -0,0 +1,35 @@
+package segments
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestElixir(t *testing.T) {
+	cases := []struct {
+		Case           string
+		ExpectedString string
+		Version        string
+	}{
+		{
+			Case:           "elixir 1.14.2",
+			ExpectedString: "1.14.2",
+			Version:        "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)",
+		},
+	}
+	for _, tc := range cases {
+		params := &mockedLanguageParams{
+			cmd:           "elixir",
+			versionParam:  "--version",
+			versionOutput: tc.Version,
+			extension:     "*.ex",
+		}
+		env, props := getMockedLanguageEnv(params)
+		r := &Elixir{}
+		r.Init(props, env)
+		assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case))
+	}
+}
diff --git a/src/segments/git_test.go b/src/segments/git_test.go
index ec048567..223d796a 100644
--- a/src/segments/git_test.go
+++ b/src/segments/git_test.go
@@ -7,6 +7,7 @@ import (
 	"path/filepath"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/jandedobbeleer/oh-my-posh/src/mock"
 	"github.com/jandedobbeleer/oh-my-posh/src/platform"
@@ -892,3 +893,85 @@ func TestGitIgnoreSubmodules(t *testing.T) {
 		assert.Equal(t, tc.Expected, got, tc.Case)
 	}
 }
+
+func TestGitCommit(t *testing.T) {
+	cases := []struct {
+		Case     string
+		Expected *Commit
+		Output   string
+	}{
+		{
+			Case: "Clean commit",
+			Output: `
+			an:Jan De Dobbeleer
+			ae:jan@ohmyposh.dev
+			cn:Jan De Dobbeleer
+			ce:jan@ohmyposh.dev
+			at:1673176335
+			su:docs(error): you can't use cross segment properties
+			`,
+			Expected: &Commit{
+				Author: &User{
+					Name:  "Jan De Dobbeleer",
+					Email: "jan@ohmyposh.dev",
+				},
+				Committer: &User{
+					Name:  "Jan De Dobbeleer",
+					Email: "jan@ohmyposh.dev",
+				},
+				Subject:   "docs(error): you can't use cross segment properties",
+				Timestamp: time.Unix(1673176335, 0),
+			},
+		},
+		{
+			Case: "No commit output",
+			Expected: &Commit{
+				Author:    &User{},
+				Committer: &User{},
+			},
+		},
+		{
+			Case: "No author",
+			Output: `
+			an:
+			ae:
+			cn:Jan De Dobbeleer
+			ce:jan@ohmyposh.dev
+			at:1673176335
+			su:docs(error): you can't use cross segment properties
+			`,
+			Expected: &Commit{
+				Author: &User{},
+				Committer: &User{
+					Name:  "Jan De Dobbeleer",
+					Email: "jan@ohmyposh.dev",
+				},
+				Subject:   "docs(error): you can't use cross segment properties",
+				Timestamp: time.Unix(1673176335, 0),
+			},
+		},
+		{
+			Case: "Bad timestamp",
+			Output: `
+			at:err
+			`,
+			Expected: &Commit{
+				Author:    &User{},
+				Committer: &User{},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.MockGitCommand("", tc.Output, "log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s")
+		g := &Git{
+			scm: scm{
+				env:     env,
+				command: "git",
+			},
+		}
+		got := g.Commit()
+		assert.Equal(t, tc.Expected, got, tc.Case)
+	}
+}
diff --git a/src/segments/spotify_windows_test.go b/src/segments/spotify_windows_test.go
index f2f9d2ba..51d75c6a 100644
--- a/src/segments/spotify_windows_test.go
+++ b/src/segments/spotify_windows_test.go
@@ -63,10 +63,16 @@ func TestSpotifyWindowsPWA(t *testing.T) {
 	}{
 		{
 			Case:            "Playing",
-			ExpectedString:  "\ue602 Snow in Stockholm - Sarah, the Illstrumentalist",
+			ExpectedString:  "\ue602 Sarah, the Illstrumentalist - Snow in Stockholm",
 			ExpectedEnabled: true,
 			Title:           "Spotify - Snow in Stockholm • Sarah, the Illstrumentalist",
 		},
+		{
+			Case:            "Playing",
+			ExpectedString:  "\ue602 Main one - Bring the drama",
+			ExpectedEnabled: true,
+			Title:           "Spotify - Bring the drama • Main one",
+		},
 		{
 			Case:            "Stopped",
 			ExpectedEnabled: false,
diff --git a/src/segments/vala_test.go b/src/segments/vala_test.go
new file mode 100644
index 00000000..60d79c43
--- /dev/null
+++ b/src/segments/vala_test.go
@@ -0,0 +1,35 @@
+package segments
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestVala(t *testing.T) {
+	cases := []struct {
+		Case           string
+		ExpectedString string
+		Version        string
+	}{
+		{
+			Case:           "vala 0.48.17",
+			ExpectedString: "0.48.17",
+			Version:        "Vala 0.48.17",
+		},
+	}
+	for _, tc := range cases {
+		params := &mockedLanguageParams{
+			cmd:           "vala",
+			versionParam:  "--version",
+			versionOutput: tc.Version,
+			extension:     "*.vala",
+		}
+		env, props := getMockedLanguageEnv(params)
+		v := &Vala{}
+		v.Init(props, env)
+		assert.True(t, v.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, v.Template(), v), fmt.Sprintf("Failed in case: %s", tc.Case))
+	}
+}