diff --git a/src/engine/segment.go b/src/engine/segment.go
index ae1d86a8..41f306d6 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -224,6 +224,8 @@ const (
TIME SegmentType = "time"
// UI5 Tooling segment
UI5TOOLING SegmentType = "ui5tooling"
+ // UNITY writes which Unity version is currently active
+ UNITY SegmentType = "unity"
// VALA writes the active vala version
VALA SegmentType = "vala"
// WAKATIME writes tracked time spend in dev editors
@@ -305,6 +307,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{} },
+ UNITY: func() SegmentWriter { return &segments.Unity{} },
VALA: func() SegmentWriter { return &segments.Vala{} },
WAKATIME: func() SegmentWriter { return &segments.Wakatime{} },
WINREG: func() SegmentWriter { return &segments.WindowsRegistry{} },
diff --git a/src/segments/unity.go b/src/segments/unity.go
new file mode 100644
index 00000000..e6c95ccd
--- /dev/null
+++ b/src/segments/unity.go
@@ -0,0 +1,112 @@
+package segments
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/jandedobbeleer/oh-my-posh/src/platform"
+ "github.com/jandedobbeleer/oh-my-posh/src/properties"
+ "github.com/jandedobbeleer/oh-my-posh/src/regex"
+)
+
+type Unity struct {
+ props properties.Properties
+ env platform.Environment
+
+ UnityVersion string
+ CSharpVersion string
+}
+
+func (u *Unity) Enabled() bool {
+ unityVersion, err := u.GetUnityVersion()
+ if err != nil {
+ u.env.Error(err)
+ return false
+ }
+ if len(unityVersion) == 0 {
+ return false
+ }
+ u.UnityVersion = unityVersion
+
+ csharpVersion, err := u.GetCSharpVersion()
+ if err != nil {
+ u.env.Error(err)
+ }
+ u.CSharpVersion = csharpVersion
+
+ return true
+}
+
+func (u *Unity) GetUnityVersion() (version string, err error) {
+ projectDir, err := u.env.HasParentFilePath("ProjectSettings")
+ if err != nil {
+ u.env.Debug("No ProjectSettings parent folder found")
+ return
+ }
+
+ if !u.env.HasFilesInDir(projectDir.Path, "ProjectVersion.txt") {
+ u.env.Debug("No ProjectVersion.txt file found")
+ return
+ }
+
+ versionFilePath := filepath.Join(projectDir.Path, "ProjectVersion.txt")
+ versionFileText := u.env.FileContent(versionFilePath)
+
+ firstLine := strings.Split(versionFileText, "\n")[0]
+ versionPrefix := "m_EditorVersion: "
+ versionPrefixIndex := strings.Index(firstLine, versionPrefix)
+ if versionPrefixIndex == -1 {
+ err := errors.New("ProjectSettings/ProjectVersion.txt is missing 'm_EditorVersion: ' prefix")
+ return "", err
+ }
+
+ versionStartIndex := versionPrefixIndex + len(versionPrefix)
+ unityVersion := firstLine[versionStartIndex:]
+
+ return strings.TrimSuffix(unityVersion, "f1"), nil
+}
+
+func (u *Unity) GetCSharpVersion() (version string, err error) {
+ lastDotIndex := strings.LastIndex(u.UnityVersion, ".")
+ if lastDotIndex == -1 {
+ return "", errors.New("lastDotIndex")
+ }
+ shortUnityVersion := u.UnityVersion[0:lastDotIndex]
+
+ if val, found := u.env.Cache().Get(shortUnityVersion); found {
+ csharpVersion := strings.TrimSuffix(val, ".0")
+ return csharpVersion, nil
+ }
+
+ url := fmt.Sprintf("https://docs.unity3d.com/%s/Documentation/Manual/CSharpCompiler.html", shortUnityVersion)
+ httpTimeout := u.props.GetInt(properties.HTTPTimeout, 2000)
+
+ body, err := u.env.HTTPRequest(url, nil, httpTimeout)
+ if err != nil {
+ return "", err
+ }
+
+ pageContent := string(body)
+
+ pattern := `(?P.*)`
+ matches := regex.FindNamedRegexMatch(pattern, pageContent)
+ if matches != nil && matches["csharpVersion"] != "" {
+ csharpVersion := strings.TrimSuffix(matches["csharpVersion"], ".0")
+ u.env.Cache().Set(shortUnityVersion, csharpVersion, -1)
+ return csharpVersion, nil
+ }
+
+ u.env.Cache().Set(shortUnityVersion, "", -1)
+ return "", nil
+}
+
+func (u *Unity) Template() string {
+ return " \ue721 {{ .UnityVersion }}{{ if .CSharpVersion }} {{ .CSharpVersion }}{{ end }} "
+}
+
+func (u *Unity) Init(props properties.Properties, env platform.Environment) {
+ u.props = props
+ u.env = env
+}
diff --git a/src/segments/unity_test.go b/src/segments/unity_test.go
new file mode 100644
index 00000000..4cb9a75e
--- /dev/null
+++ b/src/segments/unity_test.go
@@ -0,0 +1,188 @@
+package segments
+
+import (
+ "errors"
+ "fmt"
+ "path/filepath"
+ "testing"
+
+ mock2 "github.com/stretchr/testify/mock"
+
+ "github.com/jandedobbeleer/oh-my-posh/src/mock"
+ "github.com/jandedobbeleer/oh-my-posh/src/platform"
+ "github.com/jandedobbeleer/oh-my-posh/src/properties"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type CacheGet struct {
+ key string
+ val string
+ found bool
+}
+
+type CacheSet struct {
+ key string
+ val string
+}
+
+type HTTPResponse struct {
+ body string
+ err error
+}
+
+func TestUnitySegment(t *testing.T) {
+ cases := []struct {
+ Case string
+ ExpectedOutput string
+ VersionFileText string
+ CacheGet CacheGet
+ CacheSet CacheSet
+ ExpectedToBeEnabled bool
+ VersionFileExists bool
+ HTTPResponse HTTPResponse
+ }{
+ {
+ Case: "C# version cached",
+ ExpectedOutput: "\ue721 2021.3.16 C# 9",
+ ExpectedToBeEnabled: true,
+ VersionFileExists: true,
+ VersionFileText: "m_EditorVersion: 2021.3.16f1\nm_EditorVersionWithRevision: 2021.3.16f1 (4016570cf34f)",
+ CacheGet: CacheGet{
+ key: "2021.3",
+ val: "C# 9",
+ found: true,
+ },
+ },
+ {
+ Case: "C# version not cached",
+ ExpectedOutput: "\ue721 2021.3.16 C# 9",
+ ExpectedToBeEnabled: true,
+ VersionFileExists: true,
+ VersionFileText: "m_EditorVersion: 2021.3.16f1\nm_EditorVersionWithRevision: 2021.3.16f1 (4016570cf34f)",
+ CacheGet: CacheGet{
+ key: "2021.3",
+ val: "",
+ found: false,
+ },
+ CacheSet: CacheSet{
+ key: "2021.3",
+ val: "C# 9",
+ },
+ HTTPResponse: HTTPResponse{
+ body: `C# 9.0`,
+ err: nil,
+ },
+ },
+ {
+ Case: "C# version has a minor version",
+ ExpectedOutput: "\ue721 2021.3.16 C# 7.3",
+ ExpectedToBeEnabled: true,
+ VersionFileExists: true,
+ VersionFileText: "m_EditorVersion: 2021.3.16f1\nm_EditorVersionWithRevision: 2021.3.16f1 (4016570cf34f)",
+ CacheGet: CacheGet{
+ key: "2021.3",
+ val: "",
+ found: false,
+ },
+ CacheSet: CacheSet{
+ key: "2021.3",
+ val: "C# 7.3",
+ },
+ HTTPResponse: HTTPResponse{
+ body: `C# 7.3`,
+ err: nil,
+ },
+ },
+ {
+ Case: "C# version not found in webpage",
+ ExpectedOutput: "\ue721 2021.3.16",
+ ExpectedToBeEnabled: true,
+ VersionFileExists: true,
+ VersionFileText: "m_EditorVersion: 2021.3.16f1\nm_EditorVersionWithRevision: 2021.3.16f1 (4016570cf34f)",
+ CacheGet: CacheGet{
+ key: "2021.3",
+ val: "",
+ found: false,
+ },
+ CacheSet: CacheSet{
+ key: "2021.3",
+ val: "",
+ },
+ HTTPResponse: HTTPResponse{
+ body: `
Sorry... that page seems to be missing!
`,
+ err: nil,
+ },
+ },
+ {
+ Case: "http request fails",
+ ExpectedOutput: "\ue721 2021.3.16",
+ ExpectedToBeEnabled: true,
+ VersionFileExists: true,
+ VersionFileText: "m_EditorVersion: 2021.3.16f1\nm_EditorVersionWithRevision: 2021.3.16f1 (4016570cf34f)",
+ CacheGet: CacheGet{
+ key: "2021.3",
+ val: "",
+ found: false,
+ },
+ HTTPResponse: HTTPResponse{
+ body: "",
+ err: errors.New("FAIL"),
+ },
+ },
+ {
+ Case: "ProjectSettings/ProjectVersion.txt doesn't exist",
+ ExpectedToBeEnabled: false,
+ VersionFileExists: false,
+ },
+ {
+ Case: "ProjectSettings/ProjectVersion.txt is empty",
+ ExpectedToBeEnabled: false,
+ VersionFileExists: true,
+ VersionFileText: "",
+ },
+ {
+ Case: "ProjectSettings/ProjectVersion.txt does not have expected format",
+ ExpectedToBeEnabled: false,
+ VersionFileExists: true,
+ VersionFileText: "2021.3.16f1",
+ },
+ }
+
+ for _, tc := range cases {
+ env := new(mock.MockedEnvironment)
+ env.On("Error", mock2.Anything).Return()
+ env.On("Debug", mock2.Anything)
+
+ err := errors.New("no match at root level")
+ var projectDir *platform.FileInfo
+ if tc.VersionFileExists {
+ err = nil
+ projectDir = &platform.FileInfo{
+ ParentFolder: "UnityProjectRoot",
+ Path: "UnityProjectRoot/ProjectSettings",
+ IsDir: true,
+ }
+ env.On("HasFilesInDir", projectDir.Path, "ProjectVersion.txt").Return(tc.VersionFileExists)
+ versionFilePath := filepath.Join(projectDir.Path, "ProjectVersion.txt")
+ env.On("FileContent", versionFilePath).Return(tc.VersionFileText)
+ }
+ env.On("HasParentFilePath", "ProjectSettings").Return(projectDir, err)
+
+ cache := &mock.MockedCache{}
+ cache.On("Get", tc.CacheGet.key).Return(tc.CacheGet.val, tc.CacheGet.found)
+ cache.On("Set", tc.CacheSet.key, tc.CacheSet.val, -1).Return()
+ env.On("Cache").Return(cache)
+
+ url := fmt.Sprintf("https://docs.unity3d.com/%s/Documentation/Manual/CSharpCompiler.html", tc.CacheGet.key)
+ env.On("HTTPRequest", url).Return([]byte(tc.HTTPResponse.body), tc.HTTPResponse.err)
+
+ props := properties.Map{}
+ unity := &Unity{}
+ unity.Init(props, env)
+ assert.Equal(t, tc.ExpectedToBeEnabled, unity.Enabled())
+ if tc.ExpectedToBeEnabled {
+ assert.Equal(t, tc.ExpectedOutput, renderTemplate(env, unity.Template(), unity), tc.Case)
+ }
+ }
+}
diff --git a/themes/schema.json b/themes/schema.json
index a66a0b4e..0066695b 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -301,6 +301,7 @@
"text",
"terraform",
"ui5tooling",
+ "unity",
"wakatime",
"winreg",
"withings",
@@ -2752,6 +2753,26 @@
}
}
},
+ {
+ "if": {
+ "properties": {
+ "type": { "const": "unity" }
+ }
+ },
+ "then": {
+ "title": "Unity Segment",
+ "description": "https://ohmyposh.dev/docs/unity",
+ "properties": {
+ "properties": {
+ "properties": {
+ "http_timeout": {
+ "$ref": "#/definitions/http_timeout"
+ }
+ }
+ }
+ }
+ }
+ },
{
"if": {
"properties": {
diff --git a/website/docs/segments/unity.mdx b/website/docs/segments/unity.mdx
new file mode 100644
index 00000000..bc60f274
--- /dev/null
+++ b/website/docs/segments/unity.mdx
@@ -0,0 +1,59 @@
+---
+id: unity
+title: Unity
+sidebar_label: Unity
+---
+
+## What
+
+Display the currently active [Unity][unity] and C# versions.
+
+The Unity version is displayed regardless of whether or not the C# version fetching succeeds.
+
+The C# version is fetched from [the docs][unity-csharp-page], but the page only exists as far back as Unity 2018.3.
+Earlier versions will not display a C# version.
+
+A web request only occurs the first time a given `major.minor` Unity version is encountered.
+Subsequent invocations return the cached C# version.
+
+## Sample Configuration
+
+```json
+{
+ "type": "unity",
+ "style": "powerline",
+ "powerline_symbol": "\ue0b0",
+ "foreground": "#111111",
+ "background": "#ffffff",
+ "properties": {
+ "http_timeout": 2000
+ }
+}
+```
+
+## Properties
+
+| Name | Type | Description |
+| --------------- | -------- | ----------------------------------------------- |
+| `http_timeout` | `int` | The default timeout for http request is 2000ms. |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+\ue721 {{ .UnityVersion }}{{ if .CSharpVersion }} {{ .CSharpVersion }}{{ end }}
+```
+
+:::
+
+### Properties
+
+| Name | Type | Description |
+| ---------------- | -------- | ---------------------------------------- |
+| `.UnityVersion` | `string` | the Unity version |
+| `.CSharpVersion` | `string` | the C# version |
+
+[unity]: https://unity.com/
+[templates]: /docs/configuration/templates
+[unity-csharp-page]: https://docs.unity3d.com/Manual/CSharpCompiler.html
diff --git a/website/sidebars.js b/website/sidebars.js
index d8bc0f89..499ab8d6 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -115,6 +115,7 @@ module.exports = {
"segments/text",
"segments/time",
"segments/ui5tooling",
+ "segments/unity",
"segments/vala",
"segments/wakatime",
"segments/withings",