diff --git a/src/config/segment_types.go b/src/config/segment_types.go index 63df09bdc91c..e73ea08e7409 100644 --- a/src/config/segment_types.go +++ b/src/config/segment_types.go @@ -227,6 +227,8 @@ const ( YTM SegmentType = "ytm" // ZIG writes the active zig version ZIG SegmentType = "zig" + // ZVM writes the active zig version used in the zvm environment + ZVM SegmentType = "zvm" ) // Segments contains all available prompt segment writers. @@ -331,6 +333,7 @@ var Segments = map[SegmentType]func() SegmentWriter{ YARN: func() SegmentWriter { return &segments.Yarn{} }, YTM: func() SegmentWriter { return &segments.Ytm{} }, ZIG: func() SegmentWriter { return &segments.Zig{} }, + ZVM: func() SegmentWriter { return &segments.Zvm{} }, } func (segment *Segment) MapSegmentWithWriter(env runtime.Environment) error { diff --git a/src/segments/zvm.go b/src/segments/zvm.go new file mode 100644 index 000000000000..ec2dedf20405 --- /dev/null +++ b/src/segments/zvm.go @@ -0,0 +1,141 @@ +package segments + +import ( + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/properties" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" +) + +const ( + // DefaultZigIcon is the default icon used if none is specified + DefaultZigIcon = "ZVM" + + // PropertyZigIcon is the property key for the zig icon + PropertyZigIcon properties.Property = "zigicon" +) + +// Zvm represents a Zig Version Manager segment +type Zvm struct { + language + Version string // Public for template access + ZigIcon string // Public for template access + colorCmd *colorCommand +} + +type colorCommand struct { + env runtime.Environment +} + +// colorState represents the ZVM color configuration state +type colorState struct { + enabled bool + valid bool +} + +func (c *colorCommand) detectColorState() colorState { + output, err := c.env.RunCommand("zvm", "--color") + if err != nil { + return colorState{valid: false} + } + + output = strings.ToLower(strings.TrimSpace(output)) + switch output { + case "on", "yes", "y", "enabled", "true": + return colorState{enabled: true, valid: true} + case "off", "no", "n", "disabled", "false": + return colorState{enabled: false, valid: true} + default: + return colorState{valid: false} + } +} + +func (c *colorCommand) setColor(enabled bool) error { + value := "false" + if enabled { + value = "true" + } + _, err := c.env.RunCommand("zvm", "--color", value) + return err +} + +// SetText sets the version text +func (z *Zvm) SetText(text string) { + z.Version = text +} + +// Text returns the current version +func (z *Zvm) Text() string { + return z.Version +} + +// Template returns the template string for the segment +func (z *Zvm) Template() string { + return " {{ if .ZigIcon }}{{ .ZigIcon }} {{ end }}{{ .Version }} " +} + +// Init initializes the segment with the given properties and environment +func (z *Zvm) Init(props properties.Properties, env runtime.Environment) { + z.props = props + z.env = env + z.colorCmd = &colorCommand{env: env} + + z.ZigIcon = z.props.GetString(PropertyZigIcon, DefaultZigIcon) + + // Only try to get version if zvm command exists + if z.env.HasCommand("zvm") { + z.Version = z.getZvmVersion() + } +} + +// Enabled returns true if the segment should be enabled +func (z *Zvm) Enabled() bool { + if !z.env.HasCommand("zvm") { + return false + } + return z.Version != "" +} + +// getZvmVersion returns the current active Zvm version +func (z *Zvm) getZvmVersion() string { + // Detect current color state + originalState := z.colorCmd.detectColorState() + + // If we couldn't detect the state, proceed with color disabled + if !originalState.valid { + if err := z.colorCmd.setColor(false); err != nil { + return "" + } + defer func() { + _ = z.colorCmd.setColor(true) // Best effort to restore color + }() + } else if originalState.enabled { + // Temporarily disable colors if they were enabled + if err := z.colorCmd.setColor(false); err != nil { + return "" + } + defer func() { + _ = z.colorCmd.setColor(originalState.enabled) // Restore original state + }() + } + + // Get version list + output, err := z.env.RunCommand("zvm", "list") + if err != nil { + return "" + } + + return parseActiveVersion(output) +} + +// parseActiveVersion extracts the active version from zvm list output +func parseActiveVersion(output string) string { + words := strings.Fields(output) + for _, word := range words { + if !strings.Contains(word, "[x]") { + continue + } + return strings.TrimSpace(strings.ReplaceAll(word, "[x]", "")) + } + return "" +} diff --git a/src/segments/zvm_test.go b/src/segments/zvm_test.go new file mode 100644 index 000000000000..f23cec7783ca --- /dev/null +++ b/src/segments/zvm_test.go @@ -0,0 +1,154 @@ +package segments + +import ( + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/properties" + "github.com/jandedobbeleer/oh-my-posh/src/runtime/mock" + "github.com/stretchr/testify/assert" +) + +func TestZvm(t *testing.T) { + cases := []struct { + Case string + ExpectedString string + HasCommand bool + ColorState string + ListOutput string + Properties properties.Map + Template string + ExpectedIcon string + }{ + { + Case: "no zvm command", + ExpectedString: "", + HasCommand: false, + ColorState: "", + ListOutput: "", + Properties: properties.Map{}, + Template: " {{ if .ZigIcon }}{{ .ZigIcon }} {{ end }}{{ .Version }} ", + ExpectedIcon: DefaultZigIcon, + }, + { + Case: "version with colors enabled", + ExpectedString: "0.13.0", + HasCommand: true, + ColorState: "on", + ListOutput: "0.11.0\n[x]0.13.0\n0.12.0", + Properties: properties.Map{}, + Template: " {{ if .ZigIcon }}{{ .ZigIcon }} {{ end }}{{ .Version }} ", + ExpectedIcon: DefaultZigIcon, + }, + { + Case: "version with colors disabled", + ExpectedString: "0.13.0", + HasCommand: true, + ColorState: "off", + ListOutput: "0.11.0\n[x]0.13.0\n0.12.0", + Properties: properties.Map{}, + Template: " {{ if .ZigIcon }}{{ .ZigIcon }} {{ end }}{{ .Version }} ", + ExpectedIcon: DefaultZigIcon, + }, + { + Case: "version with custom icon", + ExpectedString: "0.13.0", + HasCommand: true, + ColorState: "on", + ListOutput: "0.11.0\n[x]0.13.0\n0.12.0", + Properties: properties.Map{ + PropertyZigIcon: "⚡", + }, + Template: " {{ if .ZigIcon }}{{ .ZigIcon }} {{ end }}{{ .Version }} ", + ExpectedIcon: "⚡", + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + env := new(mock.Environment) + + // Mock HasCommand first + env.On("HasCommand", "zvm").Return(tc.HasCommand) + + // Only set up other mocks if HasCommand is true + if tc.HasCommand { + // Mock color detection + env.On("RunCommand", "zvm", []string{"--color"}).Return(tc.ColorState, nil) + + // Mock color state changes based on detected state + if tc.ColorState == "on" { + env.On("RunCommand", "zvm", []string{"--color", "false"}).Return("", nil) + env.On("RunCommand", "zvm", []string{"--color", "true"}).Return("", nil) + } + + // Mock version list command + env.On("RunCommand", "zvm", []string{"list"}).Return(tc.ListOutput, nil) + } + + zvm := &Zvm{} + zvm.Init(tc.Properties, env) + + assert.Equal(t, tc.Template, zvm.Template()) + + if tc.HasCommand { + assert.True(t, zvm.Enabled()) + assert.Equal(t, tc.ExpectedString, zvm.Text()) + assert.Equal(t, tc.ExpectedIcon, zvm.ZigIcon) + } else { + assert.False(t, zvm.Enabled()) + assert.Empty(t, zvm.Text()) + } + + // Verify all expected calls were made + env.AssertExpectations(t) + }) + } +} + +func TestColorStateDetection(t *testing.T) { + cases := []struct { + Case string + ColorOutput string + Expected colorState + }{ + { + Case: "enabled - on", + ColorOutput: "on", + Expected: colorState{enabled: true, valid: true}, + }, + { + Case: "enabled - yes", + ColorOutput: "yes", + Expected: colorState{enabled: true, valid: true}, + }, + { + Case: "disabled - off", + ColorOutput: "off", + Expected: colorState{enabled: false, valid: true}, + }, + { + Case: "disabled - no", + ColorOutput: "no", + Expected: colorState{enabled: false, valid: true}, + }, + { + Case: "invalid state", + ColorOutput: "invalid", + Expected: colorState{enabled: false, valid: false}, + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + env := new(mock.Environment) + env.On("RunCommand", "zvm", []string{"--color"}).Return(tc.ColorOutput, nil) + + cmd := &colorCommand{env: env} + state := cmd.detectColorState() + + assert.Equal(t, tc.Expected.enabled, state.enabled) + assert.Equal(t, tc.Expected.valid, state.valid) + env.AssertExpectations(t) + }) + } +} diff --git a/themes/schema.json b/themes/schema.json index 1318b394b056..cf7dcd410b28 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -405,7 +405,8 @@ "xmake", "yarn", "ytm", - "zig" + "zig", + "zvm" ] }, "style": { @@ -4916,6 +4917,29 @@ } } }, + { + "if": { + "properties": { + "type": { "const": "zvm" } + } + }, + "then": { + "title": "ZVM Segment", + "description": "https://ohmyposh.dev/docs/zvm", + "properties": { + "properties": { + "properties": { + "zigicon": { + "type": "string", + "title": "Zig Icon", + "description": "icon to display before the version", + "default": "ZVM" + } + } + } + } + } + }, { "if": { "properties": { diff --git a/website/docs/segments/cli/zvm.mdx b/website/docs/segments/cli/zvm.mdx new file mode 100644 index 000000000000..5a0219d4f4ab --- /dev/null +++ b/website/docs/segments/cli/zvm.mdx @@ -0,0 +1,35 @@ +--- +id: zvm +title: ZVM +sidebar_label: ZVM +--- + +## What + +Display the current Zig version being used by zvm (Zig Version Manager). + +## Sample Configuration + + +import Config from '@site/src/components/Config.js'; + + + + +## Properties + +| Name | Type | Description | +| --------- | -------- | -------------------------------------- | +| `zigicon` | `string` | The icon to display before the version | + + +[ZVM](https://github.com/tristanisham/zvm) diff --git a/website/sidebars.js b/website/sidebars.js index d3326e30ea8b..ef4893947d2e 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -91,6 +91,7 @@ module.exports = { "segments/cli/unity", "segments/cli/xmake", "segments/cli/yarn", + "segments/cli/zvm", ] }, {