Diff
1diff --git a/cmd/soft/browse/browse.go b/cmd/soft/browse/browse.go
2index ff1a1d06178a3c6775f3da7d6f1127c0bfa45c56..c430630cf1de1a958b9871dca27e5f32ad2cbde2 100644
3--- a/cmd/soft/browse/browse.go
4+++ b/cmd/soft/browse/browse.go
5@@ -13,7 +13,6 @@ import (
6 "github.com/charmbracelet/soft-serve/pkg/ui/common"
7 "github.com/charmbracelet/soft-serve/pkg/ui/components/footer"
8 "github.com/charmbracelet/soft-serve/pkg/ui/pages/repo"
9- "github.com/muesli/termenv"
10 "github.com/spf13/cobra"
11 )
12
13@@ -40,7 +39,7 @@ var Command = &cobra.Command{
14
15 // Bubble Tea uses Termenv default output so we have to use the same
16 // thing here.
17- output := termenv.DefaultOutput()
18+ output := lipgloss.DefaultRenderer()
19 ctx := cmd.Context()
20 c := common.NewCommon(ctx, output, 0, 0)
21 c.HideCloneCmd = true
22diff --git a/cmd/soft/main.go b/cmd/soft/main.go
23index 8264abee8666d1d7424a3139be750a601f5d7f7b..9849e8a268f22fba46644af33b1ee140f79f16a2 100644
24--- a/cmd/soft/main.go
25+++ b/cmd/soft/main.go
26@@ -5,6 +5,7 @@ import (
27 "fmt"
28 "os"
29 "runtime/debug"
30+ "strconv"
31
32 "github.com/charmbracelet/log"
33 "github.com/charmbracelet/soft-serve/cmd/soft/admin"
34@@ -13,9 +14,11 @@ import (
35 "github.com/charmbracelet/soft-serve/cmd/soft/serve"
36 "github.com/charmbracelet/soft-serve/pkg/config"
37 logr "github.com/charmbracelet/soft-serve/pkg/log"
38+ "github.com/charmbracelet/soft-serve/pkg/ui/common"
39 "github.com/charmbracelet/soft-serve/pkg/version"
40 mcobra "github.com/muesli/mango-cobra"
41 "github.com/muesli/roff"
42+ "github.com/muesli/termenv"
43 "github.com/spf13/cobra"
44 "go.uber.org/automaxprocs/maxprocs"
45 )
46@@ -63,6 +66,10 @@ var (
47 )
48
49 func init() {
50+ if noColor, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_NO_COLOR")); noColor {
51+ common.DefaultColorProfile = termenv.Ascii
52+ }
53+
54 rootCmd.AddCommand(
55 manCmd,
56 serve.Command,
57diff --git a/pkg/ssh/session.go b/pkg/ssh/session.go
58index 5ccd9062b8d946f1b22edf0321a2fbfc28a2b3eb..99c8489eff56380b0d3b910b4856dc04cc5c11ec 100644
59--- a/pkg/ssh/session.go
60+++ b/pkg/ssh/session.go
61@@ -5,6 +5,7 @@ import (
62 "time"
63
64 tea "github.com/charmbracelet/bubbletea"
65+ "github.com/charmbracelet/lipgloss"
66 "github.com/charmbracelet/soft-serve/pkg/access"
67 "github.com/charmbracelet/soft-serve/pkg/backend"
68 "github.com/charmbracelet/soft-serve/pkg/config"
69@@ -54,7 +55,7 @@ func SessionHandler(s ssh.Session) *tea.Program {
70 }
71
72 envs := &sessionEnv{s}
73- output := termenv.NewOutput(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
74+ output := lipgloss.NewRenderer(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
75 c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height)
76 c.SetValue(common.ConfigKey, cfg)
77 m := NewUI(c, initialRepo)
78diff --git a/pkg/ui/common/common.go b/pkg/ui/common/common.go
79index ceee1f58ef0cee0ae55bf61299a6286da003d66e..3c3eddd303e638f5e509f2575689e6c130d95524 100644
80--- a/pkg/ui/common/common.go
81+++ b/pkg/ui/common/common.go
82@@ -4,6 +4,7 @@ import (
83 "context"
84 "fmt"
85
86+ "github.com/charmbracelet/lipgloss"
87 "github.com/charmbracelet/log"
88 "github.com/charmbracelet/soft-serve/git"
89 "github.com/charmbracelet/soft-serve/pkg/backend"
90@@ -38,7 +39,7 @@ type Common struct {
91 }
92
93 // NewCommon returns a new Common struct.
94-func NewCommon(ctx context.Context, out *termenv.Output, width, height int) Common {
95+func NewCommon(ctx context.Context, out *lipgloss.Renderer, width, height int) Common {
96 if ctx == nil {
97 ctx = context.TODO()
98 }
99@@ -46,7 +47,7 @@ func NewCommon(ctx context.Context, out *termenv.Output, width, height int) Comm
100 ctx: ctx,
101 Width: width,
102 Height: height,
103- Output: out,
104+ Output: out.Output(),
105 Styles: styles.DefaultStyles(),
106 KeyMap: keymap.DefaultKeyMap(),
107 Zone: zone.New(),
108diff --git a/testscript/script_test.go b/testscript/script_test.go
109index f7dd22eec65b699667e357bf2bbd348e9cae17fd..0b1b619e28d656363afe56e28dc6336a969e8e2d 100644
110--- a/testscript/script_test.go
111+++ b/testscript/script_test.go
112@@ -15,6 +15,7 @@ import (
113 "os/exec"
114 "path/filepath"
115 "runtime"
116+ "strconv"
117 "strings"
118 "testing"
119 "time"
120@@ -92,6 +93,8 @@ func TestScript(t *testing.T) {
121 "new-webhook": cmdNewWebhook,
122 "waitforserver": cmdWaitforserver,
123 "stopserver": cmdStopserver,
124+ "ui": cmdUI(admin1.Signer()),
125+ "uui": cmdUI(user1.Signer()),
126 },
127 Setup: func(e *testscript.Env) error {
128 // Add binPath to PATH
129@@ -120,6 +123,9 @@ func TestScript(t *testing.T) {
130 // This is used to set up test specific configuration and http endpoints
131 e.Setenv("SOFT_SERVE_TESTRUN", "1")
132
133+ // This will disable the default lipgloss renderer colors
134+ e.Setenv("SOFT_SERVE_NO_COLOR", "1")
135+
136 // Soft Serve debug environment variables
137 for _, env := range []string{
138 "SOFT_SERVE_DEBUG",
139@@ -199,6 +205,64 @@ func cmdSoft(key ssh.Signer) func(ts *testscript.TestScript, neg bool, args []st
140 }
141 }
142
143+func cmdUI(key ssh.Signer) func(ts *testscript.TestScript, neg bool, args []string) {
144+ return func(ts *testscript.TestScript, neg bool, args []string) {
145+ if len(args) < 1 {
146+ ts.Fatalf("usage: ui <quoted string input>")
147+ return
148+ }
149+
150+ cli, err := ssh.Dial(
151+ "tcp",
152+ net.JoinHostPort("localhost", ts.Getenv("SSH_PORT")),
153+ &ssh.ClientConfig{
154+ User: "git",
155+ Auth: []ssh.AuthMethod{ssh.PublicKeys(key)},
156+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
157+ },
158+ )
159+ check(ts, err, neg)
160+ defer cli.Close()
161+
162+ sess, err := cli.NewSession()
163+ check(ts, err, neg)
164+ defer sess.Close()
165+
166+ // XXX: this is a hack to make the UI tests work
167+ // cmp command always complains about an extra newline
168+ // in the output
169+ defer ts.Stdout().Write([]byte("\n"))
170+
171+ sess.Stdout = ts.Stdout()
172+ sess.Stderr = ts.Stderr()
173+
174+ stdin, err := sess.StdinPipe()
175+ check(ts, err, neg)
176+
177+ in, err := strconv.Unquote(args[0])
178+ check(ts, err, neg)
179+ reader := strings.NewReader(in)
180+ go func() {
181+ defer stdin.Close()
182+ for {
183+ r, _, err := reader.ReadRune()
184+ if err == io.EOF {
185+ break
186+ }
187+ check(ts, err, neg)
188+ stdin.Write([]byte(string(r))) // nolint: errcheck
189+
190+ // Wait for the UI to process the input
191+ time.Sleep(100 * time.Millisecond)
192+ }
193+ }()
194+
195+ err = sess.RequestPty("dumb", 40, 80, ssh.TerminalModes{})
196+ check(ts, err, neg)
197+ check(ts, sess.Run(""), neg)
198+ }
199+}
200+
201 // P.S. Windows sucks!
202 func cmdDos2Unix(ts *testscript.TestScript, neg bool, args []string) {
203 if neg {
204diff --git a/testscript/testdata/repo-blob.txtar b/testscript/testdata/repo-blob.txtar
205index 040160b17a9b9a29c020cb07502a534b5f2d0db6..e2712acfe6638c9c2eed5f98d08fbbb4c880ec3e 100644
206--- a/testscript/testdata/repo-blob.txtar
207+++ b/testscript/testdata/repo-blob.txtar
208@@ -54,6 +54,6 @@ stderr 'revision does not exist'
209 -- blob1.txt --
210 # Hello\n\nwelcome
211 -- blob2.txt --
212- [38;5;239m1[0m [38;5;236m│[0m [38;5;204m[0m[38;5;204mpackage[0m main[38;5;255m\[0mnconst foo [38;5;187m=[0m [38;5;85m2[0m[38;5;255m\[0mn
213+ 1 │ package main\nconst foo = 2\n
214 -- blob3.txt --
215 1 │ //#include <stdio.h>
216diff --git a/testscript/testdata/ui-home.txtar b/testscript/testdata/ui-home.txtar
217new file mode 100644
218index 0000000000000000000000000000000000000000..e7203fb0d969c98b958eb1d34d36289e9367a235
219--- /dev/null
220+++ b/testscript/testdata/ui-home.txtar
221@@ -0,0 +1,53 @@
222+# vi: set ft=conf
223+
224+# start soft serve
225+exec soft serve &
226+# wait for server to start
227+waitforserver
228+
229+# test repositories tab
230+ui '" q"'
231+cp stdout home.txt
232+grep 'Test Soft Serve' home.txt
233+grep '• Repositories' home.txt
234+grep 'No items found' home.txt
235+
236+# test about tab
237+ui '"\t q"'
238+cp stdout about.txt
239+grep 'Create a `.soft-serve` repository and add a `README.md` file' about.txt
240+
241+# add a new repo
242+soft repo create .soft-serve -n 'Config' -d '"Test Soft Serve"'
243+soft repo description .soft-serve
244+stdout 'Test Soft Serve'
245+soft repo project-name .soft-serve
246+stdout 'Config'
247+
248+# clone repo
249+git clone ssh://localhost:$SSH_PORT/.soft-serve config
250+
251+# create readme file
252+mkfile ./config/README.md '# Hello World\nTest Soft Serve'
253+git -C config add -A
254+git -C config commit -m 'Initial commit'
255+git -C config push origin HEAD
256+
257+# test repositories tab
258+ui '" q"'
259+cp stdout home2.txt
260+grep 'Config' home2.txt
261+grep 'Test Soft Serve' home2.txt
262+grep 'git clone ssh://localhost:.*/.soft-serve' home2.txt
263+
264+# test about tab
265+ui '"\t q"'
266+cp stdout about2.txt
267+grep '• About' about2.txt
268+grep 'Hello World' about2.txt
269+grep 'Test Soft Serve' about2.txt
270+
271+# stop the server
272+[windows] stopserver
273+[windows] ! stderr .
274+