Skip to content

Commit e122f89

Browse files
authored
feat(tui): add styled output with lipgloss (#107)
1 parent d2c0b8a commit e122f89

14 files changed

Lines changed: 700 additions & 127 deletions

File tree

go.mod

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,28 @@ go 1.23
44

55
require (
66
github.com/briandowns/spinner v1.23.2
7+
github.com/charmbracelet/lipgloss v1.1.0
78
github.com/fatih/color v1.18.0
9+
github.com/muesli/termenv v0.16.0
810
github.com/schollz/progressbar/v3 v3.18.0
911
github.com/spf13/cobra v1.10.1
10-
golang.org/x/sys v0.29.0
12+
golang.org/x/sys v0.30.0
1113
)
1214

1315
require (
16+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
17+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
18+
github.com/charmbracelet/x/ansi v0.8.0 // indirect
19+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
20+
github.com/charmbracelet/x/term v0.2.1 // indirect
1421
github.com/inconshreveable/mousetrap v1.1.0 // indirect
22+
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
1523
github.com/mattn/go-colorable v0.1.13 // indirect
1624
github.com/mattn/go-isatty v0.0.20 // indirect
25+
github.com/mattn/go-runewidth v0.0.16 // indirect
1726
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
1827
github.com/rivo/uniseg v0.4.7 // indirect
1928
github.com/spf13/pflag v1.0.10 // indirect
29+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
2030
golang.org/x/term v0.28.0 // indirect
2131
)

go.sum

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
2+
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
13
github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w=
24
github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
5+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
6+
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
7+
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
8+
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
9+
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
10+
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
11+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
12+
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
13+
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
14+
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
315
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
416
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
517
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -9,6 +21,8 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
921
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
1022
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1123
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
24+
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
25+
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
1226
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
1327
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
1428
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -18,8 +32,11 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
1832
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
1933
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
2034
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
35+
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
36+
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
2137
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2238
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
39+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
2340
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
2441
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
2542
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -32,10 +49,14 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
3249
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
3350
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
3451
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
52+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
53+
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
54+
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
55+
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
3556
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
3657
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
37-
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
38-
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
58+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
59+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
3960
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
4061
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
4162
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"build": "npm run build:cli && npm run build:shim",
1313
"build:cli": "go build -v -ldflags=\"-s -w\" -o dist/dtvem.exe ./src",
1414
"build:shim": "go build -v -ldflags=\"-s -w\" -o dist/dtvem-shim.exe ./src/cmd/shim",
15-
"install": "npm run build && cp dist/dtvem.exe ~/.dtvem/bin/dtvem.exe && cp dist/dtvem-shim.exe ~/.dtvem/bin/dtvem-shim.exe && ~/.dtvem/bin/dtvem.exe reshim",
15+
"deploy:local": "npm run build && copy dist\\dtvem.exe %USERPROFILE%\\.dtvem\\bin\\dtvem.exe && copy dist\\dtvem-shim.exe %USERPROFILE%\\.dtvem\\bin\\dtvem-shim.exe && echo Deploy complete. Run 'dtvem reshim' manually to update shims.",
1616
"clean": "rm -rf dist coverage.out coverage.html",
1717
"check": "npm run format && npm run lint && npm run test"
1818
},

src/cmd/current.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/dtvem/dtvem/src/internal/runtime"
7+
"github.com/dtvem/dtvem/src/internal/tui"
78
"github.com/dtvem/dtvem/src/internal/ui"
89
"github.com/spf13/cobra"
910
)
@@ -66,17 +67,21 @@ func showAllVersions() {
6667
}
6768

6869
// Display all configured versions
69-
ui.Header("Currently active versions:")
70+
table := tui.NewTable("Runtime", "Version", "Status")
71+
table.SetTitle("Active Versions")
7072
var missing []runtimeStatus
73+
7174
for _, rs := range configured {
7275
if rs.installed {
73-
fmt.Printf(" %s: %s\n", ui.Highlight(rs.provider.DisplayName()), ui.HighlightVersion(rs.version))
76+
table.AddActiveRow(rs.provider.DisplayName(), rs.version, tui.CheckMark+" installed")
7477
} else {
75-
ui.Warning("%s: %s (not installed)", rs.provider.DisplayName(), rs.version)
78+
table.AddRow(rs.provider.DisplayName(), rs.version, tui.CrossMark+" not installed")
7679
missing = append(missing, rs)
7780
}
7881
}
7982

83+
fmt.Println(table.Render())
84+
8085
// Prompt to install missing versions
8186
if len(missing) > 0 {
8287
fmt.Println()
@@ -109,13 +114,18 @@ func showSingleVersion(runtimeName string) {
109114
}
110115

111116
installed, _ := provider.IsInstalled(version)
117+
118+
table := tui.NewTable("Runtime", "Version", "Status")
112119
if installed {
113-
fmt.Printf("%s: %s\n", ui.Highlight(provider.DisplayName()), ui.HighlightVersion(version))
120+
table.AddActiveRow(provider.DisplayName(), version, tui.CheckMark+" installed")
121+
fmt.Println(table.Render())
114122
return
115123
}
116124

117125
// Not installed - show with warning and prompt
118-
ui.Warning("%s: %s (not installed)", provider.DisplayName(), version)
126+
table.AddRow(provider.DisplayName(), version, tui.CrossMark+" not installed")
127+
fmt.Println(table.Render())
128+
119129
fmt.Println()
120130
if ui.PromptInstall(provider.DisplayName(), version) {
121131
if err := provider.Install(version); err != nil {

src/cmd/list.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package cmd
22

33
import (
4+
"fmt"
5+
46
"github.com/dtvem/dtvem/src/internal/config"
57
"github.com/dtvem/dtvem/src/internal/runtime"
8+
"github.com/dtvem/dtvem/src/internal/tui"
69
"github.com/dtvem/dtvem/src/internal/ui"
710
"github.com/spf13/cobra"
811
)
@@ -41,8 +44,6 @@ func listAllRuntimes() {
4144
return
4245
}
4346

44-
ui.Header("Installed versions:")
45-
4647
hasAny := false
4748
for _, provider := range providers {
4849
versions, err := provider.ListInstalled()
@@ -60,10 +61,23 @@ func listAllRuntimes() {
6061
globalVersion, _ := provider.GlobalVersion()
6162
localVersion, _ := config.LocalVersion(runtimeName)
6263

63-
ui.Printf(" %s:\n", ui.Highlight(provider.DisplayName()))
64+
// Create table for this runtime with title
65+
table := tui.NewTable("Version", "Status")
66+
table.SetTitle(provider.DisplayName())
67+
6468
for _, v := range versions {
65-
printVersionLine(v.String(), globalVersion, localVersion)
69+
version := v.String()
70+
status := getVersionStatus(version, globalVersion, localVersion)
71+
isActive := isVersionActive(version, globalVersion, localVersion)
72+
73+
if isActive {
74+
table.AddActiveRow(version, status)
75+
} else {
76+
table.AddRow(version, status)
77+
}
6678
}
79+
80+
fmt.Println(table.Render())
6781
}
6882

6983
if !hasAny {
@@ -80,8 +94,6 @@ func listSingleRuntime(runtimeName string) {
8094
return
8195
}
8296

83-
ui.Header("Installed %s versions:", provider.DisplayName())
84-
8597
versions, err := provider.ListInstalled()
8698
if err != nil {
8799
ui.Error("%v", err)
@@ -96,36 +108,57 @@ func listSingleRuntime(runtimeName string) {
96108
globalVersion, _ := provider.GlobalVersion()
97109
localVersion, _ := config.LocalVersion(runtimeName)
98110

111+
// Create table with title
112+
table := tui.NewTable("Version", "Status")
113+
table.SetTitle(provider.DisplayName())
114+
99115
for _, v := range versions {
100-
printVersionLine(v.String(), globalVersion, localVersion)
116+
version := v.String()
117+
status := getVersionStatus(version, globalVersion, localVersion)
118+
isActive := isVersionActive(version, globalVersion, localVersion)
119+
120+
if isActive {
121+
table.AddActiveRow(version, status)
122+
} else {
123+
table.AddRow(version, status)
124+
}
101125
}
126+
127+
fmt.Println(table.Render())
102128
}
103129

104-
// printVersionLine prints a single version with appropriate indicators and colors
105-
// Active version (local > global) is shown in green
106-
// Indicators: 🌐 for global, 📍 for local
107-
func printVersionLine(version, globalVersion, localVersion string) {
130+
// getVersionStatus returns a status string for a version (global, local, or empty)
131+
func getVersionStatus(version, globalVersion, localVersion string) string {
108132
isGlobal := version == globalVersion
109133
isLocal := version == localVersion
110134

111-
// Determine if this is the active version (local takes priority over global)
112-
isActive := isLocal || (isGlobal && localVersion == "")
113-
114-
// Build the indicator string
115-
var indicators string
135+
var parts []string
116136
if isLocal {
117-
indicators += " " + localIndicator
137+
parts = append(parts, localIndicator+" local")
118138
}
119139
if isGlobal {
120-
indicators += " " + globalIndicator
140+
parts = append(parts, globalIndicator+" global")
121141
}
122142

123-
// Format and print
124-
if isActive {
125-
ui.Printf(" %s%s\n", ui.ActiveVersion(version), indicators)
126-
} else {
127-
ui.Printf(" %s%s\n", version, indicators)
143+
if len(parts) == 0 {
144+
return ""
145+
}
146+
147+
status := ""
148+
for i, p := range parts {
149+
if i > 0 {
150+
status += ", "
151+
}
152+
status += p
128153
}
154+
return status
155+
}
156+
157+
// isVersionActive returns true if this version is the currently active one
158+
func isVersionActive(version, globalVersion, localVersion string) bool {
159+
isGlobal := version == globalVersion
160+
isLocal := version == localVersion
161+
return isLocal || (isGlobal && localVersion == "")
129162
}
130163

131164
func init() {

0 commit comments

Comments
 (0)