Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/config/segment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
141 changes: 141 additions & 0 deletions src/segments/zvm.go
Original file line number Diff line number Diff line change
@@ -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 ""
}
154 changes: 154 additions & 0 deletions src/segments/zvm_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
26 changes: 25 additions & 1 deletion themes/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@
"xmake",
"yarn",
"ytm",
"zig"
"zig",
"zvm"
]
},
"style": {
Expand Down Expand Up @@ -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": {
Expand Down
35 changes: 35 additions & 0 deletions website/docs/segments/cli/zvm.mdx
Original file line number Diff line number Diff line change
@@ -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';


<Config data={{
"type": "zvm",
"style": "powerline",
"powerline_symbol": "\uE0B0",
"foreground": "#F7A41D",
"background": "#193549",
"properties": {
"zigicon": "ZVM - "
}
}}/>

## Properties

| Name | Type | Description |
| --------- | -------- | -------------------------------------- |
| `zigicon` | `string` | The icon to display before the version |


[ZVM](https://github.com/tristanisham/zvm)
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ module.exports = {
"segments/cli/unity",
"segments/cli/xmake",
"segments/cli/yarn",
"segments/cli/zvm",
]
},
{
Expand Down