diff --git a/docs/docs/segment-haskell.md b/docs/docs/segment-haskell.md new file mode 100644 index 00000000..a9a566fe --- /dev/null +++ b/docs/docs/segment-haskell.md @@ -0,0 +1,63 @@ +--- +id: haskell +title: Haskell +sidebar_label: Haskell +--- + +## What + +Display the currently active Glasgow Haskell Compiler (GHC) version. + +## Sample Configuration + +```json +{ + "type": "haskell", + "style": "powerline", + "powerline_symbol": "\uE0B0", + "foreground": "#906cff", + "background": "#100e23", + "properties": { + "template": " \ue61f {{ .Full }}" + } +} +``` + +## Properties + +- home_enabled: `boolean` - display the segment in the HOME folder or not - defaults to `false` +- fetch_version: `boolean` - display the GHC version - defaults to `true` +- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true` +- missing_command_text: `string` - text to display when the command is missing - defaults to empty +- display_mode: `string` - determines when the segment is displayed + - `always`: the segment is always displayed + - `files`: the segment is only displayed when `*.hs`, `*.lhs`, `stack.yaml`, `package.yaml`, `*.cabal`, +or `cabal.project` files are present (default) +- stack_ghc_mode: `string` - determines when to use stack ghc to retrieve the version information. +Using stack ghc will decrease performance. + - `never`: never use stack ghc (default) + - `package`: only use stack ghc when `stack.yaml` is in the root of the package + - `always`: always use stack ghc + +## Template ([info][templates]) + +:::note default template + +```template +{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} +``` + +::: + +### Properties + +- `.Full`: `string` - the full version +- `.Major`: `string` - major number +- `.Minor`: `string` - minor number +- `.Patch`: `string` - patch number +- `.Prerelease`: `string` - prerelease info text +- `.BuildMetadata`: `string` - build metadata +- `.Error`: `string` - when fetching the version string errors +- `.StackGhc`: `boolean` - `true` if stack ghc was used, otherwise `false` + +[templates]: /docs/config-templates diff --git a/docs/sidebars.js b/docs/sidebars.js index 4fb09b2e..b3550d80 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -51,6 +51,7 @@ module.exports = { "git", "poshgit", "golang", + "haskell", "ipify", "java", "julia", diff --git a/src/engine/segment.go b/src/engine/segment.go index c6af7420..899210b0 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -147,6 +147,8 @@ const ( BREWFATHER SegmentType = "brewfather" // IPIFY segment IPIFY SegmentType = "ipify" + // HASKELL segment + HASKELL SegmentType = "haskell" ) func (segment *Segment) shouldIncludeFolder() bool { @@ -263,6 +265,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error WINREG: &segments.WindowsRegistry{}, BREWFATHER: &segments.Brewfather{}, IPIFY: &segments.IPify{}, + HASKELL: &segments.Haskell{}, } if segment.Properties == nil { segment.Properties = make(properties.Map) diff --git a/src/segments/haskell.go b/src/segments/haskell.go new file mode 100644 index 00000000..4bba90a1 --- /dev/null +++ b/src/segments/haskell.go @@ -0,0 +1,59 @@ +package segments + +import ( + "oh-my-posh/environment" + "oh-my-posh/properties" +) + +type Haskell struct { + language + + StackGhc bool +} + +const ( + StackGhcMode properties.Property = "stack_ghc_mode" +) + +func (h *Haskell) Template() string { + return languageTemplate +} + +func (h *Haskell) Init(props properties.Properties, env environment.Environment) { + ghcRegex := `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))` + ghcCmd := &cmd{ + executable: "ghc", + args: []string{"--numeric-version"}, + regex: ghcRegex, + } + + stackGhcCmd := &cmd{ + executable: "stack", + args: []string{"ghc", "--", "--numeric-version"}, + regex: ghcRegex, + } + + h.language = language{ + env: env, + props: props, + extensions: []string{"*.hs", "*.lhs", "stack.yaml", "package.yaml", "*.cabal", "cabal.project"}, + commands: []*cmd{ghcCmd}, + versionURLTemplate: "https://www.haskell.org/ghc/download_ghc_{{ .Major }}_{{ .Minor }}_{{ .Patch }}.html", + } + + switch h.props.GetString(StackGhcMode, "never") { + case "always": + h.language.commands = []*cmd{stackGhcCmd} + h.StackGhc = true + case "package": + _, err := h.language.env.HasParentFilePath("stack.yaml") + if err == nil { + h.language.commands = []*cmd{stackGhcCmd} + h.StackGhc = true + } + } +} + +func (h *Haskell) Enabled() bool { + return h.language.Enabled() +} diff --git a/src/segments/haskell_test.go b/src/segments/haskell_test.go new file mode 100644 index 00000000..3752059f --- /dev/null +++ b/src/segments/haskell_test.go @@ -0,0 +1,97 @@ +package segments + +import ( + "errors" + "fmt" + "oh-my-posh/environment" + "oh-my-posh/mock" + "oh-my-posh/properties" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHaskell(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + GhcVersion string + StackGhcVersion string + StackGhcMode string + InStackPackage bool + StackGhc bool + }{ + { + Case: "GHC 8.10.7", + ExpectedString: "8.10.7", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "never", + }, + { + Case: "Stack GHC Mode - Always", + ExpectedString: "9.0.2", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "always", + StackGhc: true, + }, + { + Case: "Stack GHC Mode - Package", + ExpectedString: "9.0.2", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "package", + InStackPackage: true, + StackGhc: true, + }, + { + Case: "Stack GHC Mode - Package no stack.yaml", + ExpectedString: "8.10.7", + GhcVersion: "8.10.7", + StackGhcVersion: "9.0.2", + StackGhcMode: "package", + }, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + if tc.StackGhcMode == "always" || (tc.StackGhcMode == "package" && tc.InStackPackage) { + env.On("HasCommand", "stack").Return(true) + env.On("RunCommand", "stack", []string{"ghc", "--", "--numeric-version"}).Return(tc.StackGhcVersion, nil) + } else { + env.On("HasCommand", "ghc").Return(true) + env.On("RunCommand", "ghc", []string{"--numeric-version"}).Return(tc.GhcVersion, nil) + } + fileInfo := &environment.FileInfo{ + Path: "../stack.yaml", + ParentFolder: "./", + IsDir: false, + } + if tc.InStackPackage { + var err error + env.On("HasParentFilePath", "stack.yaml").Return(fileInfo, err) + } else { + env.On("HasParentFilePath", "stack.yaml").Return(fileInfo, errors.New("no match")) + } + env.On("HasFiles", "*.hs").Return(true) + env.On("Pwd").Return("/usr/home/project") + env.On("Home").Return("/usr/home") + env.On("TemplateCache").Return(&environment.TemplateCache{ + Env: make(map[string]string), + }) + + props := properties.Map{ + properties.FetchVersion: true, + } + props[StackGhcMode] = tc.StackGhcMode + + h := &Haskell{} + h.Init(props, env) + + failMsg := fmt.Sprintf("Failed in case: %s", tc.Case) + assert.True(t, h.Enabled(), failMsg) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, h.Template(), h), failMsg) + assert.Equal(t, tc.StackGhc, h.StackGhc, failMsg) + } +} diff --git a/themes/schema.json b/themes/schema.json index 7612eac0..d5005d46 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -182,7 +182,8 @@ "wifi", "winreg", "plastic", - "ipify" + "ipify", + "haskell" ] }, "style": { @@ -1872,6 +1873,39 @@ } } } + }, + { + "if": { + "properties": { + "type": { "const": "haskell" } + } + }, + "then": { + "title": "Haskell Segment", + "description": "https://ohmyposh.dev/docs/haskell", + "properties": { + "properties": { + "properties": { + "fetch_version": { + "$ref": "#/definitions/fetch_version" + }, + "stack_ghc_mode": { + "type": "string", + "title": "Use Stack GHC", + "description": "Get the GHC version used by Stack. Will decrease performance. Boolean indicating whether stack ghc was used available in template as .StackGhc", + "enum": ["always", "package", "never"], + "default": "never" + }, + "display_mode": { + "$ref": "#/definitions/display_mode" + }, + "missing_command_text": { + "$ref": "#/definitions/missing_command_text" + } + } + } + } + } } ] }