feat: update wish

Carlos Alexandro Becker created

- upgrade wish
- using new color profile et al
- moved some `lipgloss.NewStyle` usages to `renderer.NewStyle`
- minor cleanup
- for now, added a `*lipgloss.Renderer` to `Styles`, eventually should
  probably move all styles to the struct and remove it (hence why I
  added it as deprecated)

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

Change summary

go.mod                                   |  10 +
go.sum                                   |  29 ++--
pkg/ssh/cmd/blob.go                      |   5 
pkg/ssh/cmd/commit.go                    |   5 
pkg/ssh/cmd/repo.go                      |   7 
pkg/ssh/middleware.go                    |   5 
pkg/ssh/session.go                       |  39 +----
pkg/ui/common/common.go                  |   2 
pkg/ui/components/code/code.go           |   3 
pkg/ui/components/statusbar/statusbar.go |   2 
pkg/ui/components/tabs/tabs.go           |   2 
pkg/ui/pages/repo/filesitem.go           |   2 
pkg/ui/pages/repo/repo.go                |   6 
pkg/ui/pages/selection/selection.go      |   9 
pkg/ui/styles/styles.go                  | 164 +++++++++++++------------
15 files changed, 141 insertions(+), 149 deletions(-)

Detailed changes

go.mod 🔗

@@ -5,10 +5,10 @@ go 1.20
 require (
 	github.com/alecthomas/chroma v0.10.0
 	github.com/charmbracelet/bubbles v0.16.1
-	github.com/charmbracelet/bubbletea v0.24.2
+	github.com/charmbracelet/bubbletea v0.25.0
 	github.com/charmbracelet/glamour v0.6.0
 	github.com/charmbracelet/lipgloss v0.9.1
-	github.com/charmbracelet/wish v1.2.0
+	github.com/charmbracelet/wish v1.3.0
 	github.com/dustin/go-humanize v1.0.1
 	github.com/go-git/go-git/v5 v5.11.0
 	github.com/matryer/is v1.4.1
@@ -25,7 +25,7 @@ require (
 	github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0
 	github.com/charmbracelet/keygen v0.5.0
 	github.com/charmbracelet/log v0.3.1
-	github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09
+	github.com/charmbracelet/ssh v0.0.0-20240130181001-ea1d614a1855
 	github.com/go-jose/go-jose/v3 v3.0.1
 	github.com/gobwas/glob v0.2.3
 	github.com/golang-jwt/jwt/v5 v5.2.0
@@ -57,7 +57,10 @@ require (
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 // indirect
+	github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60 // indirect
 	github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
+	github.com/creack/pty v1.1.21 // indirect
 	github.com/dlclark/regexp2 v1.4.0 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
@@ -85,6 +88,7 @@ require (
 	github.com/rivo/uniseg v0.4.3 // indirect
 	github.com/sahilm/fuzzy v0.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
+	github.com/u-root/u-root v0.11.0 // indirect
 	github.com/yuin/goldmark v1.5.2 // indirect
 	github.com/yuin/goldmark-emoji v1.0.1 // indirect
 	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect

go.sum 🔗

@@ -23,8 +23,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
 github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
-github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
-github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
+github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
+github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0 h1:Wi80d2xNAqvi6r8Udlc9UgPMdrJ+ld5ylKH7d8SQ7gE=
 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20231027181609-f7ff6baf2ed0/go.mod h1:AHLgIZ2TXQCgt3pDNXR6FgmpGHLH1wPM9cEJPHSVaYg=
 github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
@@ -35,13 +35,19 @@ github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1
 github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
 github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw=
 github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g=
-github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09 h1:ZDIQmTtohv0S/AAYE//w8mYTxCzqphhF1+4ACPDMiLU=
-github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09/go.mod h1:F1vgddWsb/Yr/OZilFeRZEh5sE/qU0Dt1mKkmke6Zvg=
-github.com/charmbracelet/wish v1.2.0 h1:h5Wj9pr97IQz/l4gM5Xep2lXcY/YM+6O2RC2o3x0JIQ=
-github.com/charmbracelet/wish v1.2.0/go.mod h1:JX3fC+178xadJYAhPu6qWtVDpJTwpnFvpdjz9RKJlUE=
+github.com/charmbracelet/ssh v0.0.0-20240130181001-ea1d614a1855 h1:i6Ceyw+Dnsc+1t0nwgcUc+hz/sJ2RlZPhwvZMfTgGpI=
+github.com/charmbracelet/ssh v0.0.0-20240130181001-ea1d614a1855/go.mod h1:IHy7o73i1MrQ5lmyJjjJ0g7y4+V+g69cm+Y7JCiZWPo=
+github.com/charmbracelet/wish v1.3.0 h1:SYV5TIlzDb6WaxjkkYXxv2WZsTu/QZGwfGVc0UB5M48=
+github.com/charmbracelet/wish v1.3.0/go.mod h1:1U/bI7zX+IE26ThD5gxtLgeRzctVhSrTpjucPqw4Pos=
+github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 h1:3RXpZWGWTOeVXCTv0Dnzxdv/MhNUkBfEcbaTY0zrTQI=
+github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
+github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60 h1:IV19YKUZVf6ATrhiPSCirZ4Bs7EsenYwOWcUHngV+q0=
+github.com/charmbracelet/x/exp/term v0.0.0-20240130180102-bafe6fbaee60/go.mod h1:kOOxxyxgAFQVcR5yQJWTuLjzt5dR2pcgwy3WaLEudjE=
 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
+github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -186,6 +192,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/u-root/gobusybox/src v0.0.0-20221229083637-46b2883a7f90 h1:zTk5683I9K62wtZ6eUa6vu6IWwVHXPnoKK5n2unAwv0=
+github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8=
+github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
 github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
@@ -195,7 +204,6 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
 go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
 golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
@@ -203,7 +211,6 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQz
 golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
 golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
 golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
@@ -212,23 +219,17 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
 golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

pkg/ssh/cmd/blob.go 🔗

@@ -3,6 +3,7 @@ package cmd
 import (
 	"fmt"
 
+	"github.com/charmbracelet/lipgloss"
 	"github.com/charmbracelet/soft-serve/git"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
@@ -11,12 +12,12 @@ import (
 )
 
 // blobCommand returns a command that prints the contents of a file.
-func blobCommand() *cobra.Command {
+func blobCommand(renderer *lipgloss.Renderer) *cobra.Command {
 	var linenumber bool
 	var color bool
 	var raw bool
 
-	styles := styles.DefaultStyles()
+	styles := styles.DefaultStyles(renderer)
 	cmd := &cobra.Command{
 		Use:               "blob REPOSITORY [REFERENCE] [PATH]",
 		Aliases:           []string{"cat", "show"},

pkg/ssh/cmd/commit.go 🔗

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	gansi "github.com/charmbracelet/glamour/ansi"
+	"github.com/charmbracelet/lipgloss"
 	"github.com/charmbracelet/soft-serve/git"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
@@ -14,7 +15,7 @@ import (
 )
 
 // commitCommand returns a command that prints the contents of a commit.
-func commitCommand() *cobra.Command {
+func commitCommand(renderer *lipgloss.Renderer) *cobra.Command {
 	var color bool
 	var patchOnly bool
 
@@ -54,7 +55,7 @@ func commitCommand() *cobra.Command {
 				return err
 			}
 
-			commonStyle := styles.DefaultStyles()
+			commonStyle := styles.DefaultStyles(renderer)
 			style := commonStyle.Log
 
 			s := strings.Builder{}

pkg/ssh/cmd/repo.go 🔗

@@ -4,13 +4,14 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/charmbracelet/lipgloss"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/proto"
 	"github.com/spf13/cobra"
 )
 
 // RepoCommand returns a command for managing repositories.
-func RepoCommand() *cobra.Command {
+func RepoCommand(renderer *lipgloss.Renderer) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:     "repo",
 		Aliases: []string{"repos", "repository", "repositories"},
@@ -18,10 +19,10 @@ func RepoCommand() *cobra.Command {
 	}
 
 	cmd.AddCommand(
-		blobCommand(),
+		blobCommand(renderer),
 		branchCommand(),
 		collabCommand(),
-		commitCommand(),
+		commitCommand(renderer),
 		createCommand(),
 		deleteCommand(),
 		descriptionCommand(),

pkg/ssh/middleware.go 🔗

@@ -14,6 +14,7 @@ import (
 	"github.com/charmbracelet/soft-serve/pkg/store"
 	"github.com/charmbracelet/ssh"
 	"github.com/charmbracelet/wish"
+	bm "github.com/charmbracelet/wish/bubbletea"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 	"github.com/spf13/cobra"
@@ -84,6 +85,8 @@ func CommandMiddleware(sh ssh.Handler) ssh.Handler {
 				return
 			}
 
+			r := bm.MakeRenderer(s)
+
 			ctx := s.Context()
 			cfg := config.FromContext(ctx)
 
@@ -101,7 +104,7 @@ func CommandMiddleware(sh ssh.Handler) ssh.Handler {
 				cmd.GitUploadPackCommand(),
 				cmd.GitUploadArchiveCommand(),
 				cmd.GitReceivePackCommand(),
-				cmd.RepoCommand(),
+				cmd.RepoCommand(r),
 				cmd.SettingsCommand(),
 				cmd.UserCommand(),
 				cmd.InfoCommand(),

pkg/ssh/session.go 🔗

@@ -1,11 +1,9 @@
 package ssh
 
 import (
-	"strings"
 	"time"
 
 	tea "github.com/charmbracelet/bubbletea"
-	"github.com/charmbracelet/lipgloss"
 	"github.com/charmbracelet/soft-serve/pkg/access"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/config"
@@ -13,7 +11,7 @@ import (
 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 	"github.com/charmbracelet/ssh"
 	"github.com/charmbracelet/wish"
-	"github.com/muesli/termenv"
+	bm "github.com/charmbracelet/wish/bubbletea"
 	"github.com/prometheus/client_golang/prometheus"
 	"github.com/prometheus/client_golang/prometheus/promauto"
 )
@@ -54,19 +52,18 @@ func SessionHandler(s ssh.Session) *tea.Program {
 		}
 	}
 
-	envs := &sessionEnv{s}
-	output := lipgloss.NewRenderer(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs))
-	c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height)
-	c.SetValue(common.ConfigKey, cfg)
-	m := NewUI(c, initialRepo)
-	p := tea.NewProgram(m,
-		tea.WithInput(s),
-		tea.WithOutput(s),
+	output := bm.MakeRenderer(s)
+	opts := append(
+		bm.MakeOptions(s),
 		tea.WithAltScreen(),
 		tea.WithoutCatchPanics(),
 		tea.WithMouseCellMotion(),
 		tea.WithContext(ctx),
 	)
+	c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height)
+	c.SetValue(common.ConfigKey, cfg)
+	m := NewUI(c, initialRepo)
+	p := tea.NewProgram(m, opts...)
 
 	tuiSessionCounter.WithLabelValues(initialRepo, pty.Term).Inc()
 
@@ -78,23 +75,3 @@ func SessionHandler(s ssh.Session) *tea.Program {
 
 	return p
 }
-
-var _ termenv.Environ = &sessionEnv{}
-
-type sessionEnv struct {
-	ssh.Session
-}
-
-func (s *sessionEnv) Environ() []string {
-	pty, _, _ := s.Pty()
-	return append(s.Session.Environ(), "TERM="+pty.Term)
-}
-
-func (s *sessionEnv) Getenv(key string) string {
-	for _, env := range s.Environ() {
-		if strings.HasPrefix(env, key+"=") {
-			return strings.TrimPrefix(env, key+"=")
-		}
-	}
-	return ""
-}

pkg/ui/common/common.go 🔗

@@ -48,7 +48,7 @@ func NewCommon(ctx context.Context, out *lipgloss.Renderer, width, height int) C
 		Width:  width,
 		Height: height,
 		Output: out.Output(),
-		Styles: styles.DefaultStyles(),
+		Styles: styles.DefaultStyles(out),
 		KeyMap: keymap.DefaultKeyMap(),
 		Zone:   zone.New(),
 		Logger: log.FromContext(ctx).WithPrefix("ui"),

pkg/ui/components/code/code.go 🔗

@@ -121,7 +121,7 @@ func (r *Code) Init() tea.Cmd {
 	// https://github.com/muesli/reflow/issues/43
 	//
 	// TODO: solve this upstream in Glamour/Reflow.
-	content = lipgloss.NewStyle().Width(w).Render(content)
+	content = r.common.Styles.Renderer.NewStyle().Width(w).Render(content)
 
 	r.Viewport.Model.SetContent(content)
 
@@ -213,7 +213,6 @@ func (r *Code) glamourize(w int, md string) (string, error) {
 		glamour.WithStyles(r.styleConfig),
 		glamour.WithWordWrap(w),
 	)
-
 	if err != nil {
 		return "", err
 	}

pkg/ui/components/statusbar/statusbar.go 🔗

@@ -80,7 +80,7 @@ func (s *Model) View() string {
 		Width(maxWidth).
 		Render(v)
 
-	return lipgloss.NewStyle().MaxWidth(s.common.Width).
+	return s.common.Styles.Renderer.NewStyle().MaxWidth(s.common.Width).
 		Render(
 			lipgloss.JoinHorizontal(lipgloss.Top,
 				key,

pkg/ui/components/tabs/tabs.go 🔗

@@ -105,7 +105,7 @@ func (t *Tabs) View() string {
 			s.WriteString(sep.String())
 		}
 	}
-	return lipgloss.NewStyle().
+	return t.common.Styles.Renderer.NewStyle().
 		MaxWidth(t.common.Width).
 		Render(s.String())
 }

pkg/ui/pages/repo/filesitem.go 🔗

@@ -139,7 +139,7 @@ func (d FileItemDelegate) Render(w io.Writer, m list.Model, index int, listItem
 	name = nameStyle.Render(name)
 	size = sizeStyle.Render(size)
 	modeStr := modeStyle.Render(mode.String())
-	truncate := lipgloss.NewStyle().MaxWidth(m.Width() -
+	truncate := d.common.Styles.Renderer.NewStyle().MaxWidth(m.Width() -
 		s.Selector.GetHorizontalFrameSize() -
 		s.Selector.GetWidth())
 	fmt.Fprint(w,

pkg/ui/pages/repo/repo.go 🔗

@@ -189,12 +189,12 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			switch msg.Type {
 			case tea.MouseLeft:
 				switch {
-				case r.common.Zone.Get("repo-help").InBounds(msg):
+				case r.common.Zone.Get("repo - help").InBounds(msg):
 					cmds = append(cmds, footer.ToggleFooterCmd)
 				}
 			case tea.MouseRight:
 				switch {
-				case r.common.Zone.Get("repo-main").InBounds(msg):
+				case r.common.Zone.Get("repo - main").InBounds(msg):
 					cmds = append(cmds, goBackCmd)
 				}
 			}
@@ -323,7 +323,7 @@ func (r *Repo) headerView() string {
 	if r.selectedRepo == nil {
 		return ""
 	}
-	truncate := lipgloss.NewStyle().MaxWidth(r.common.Width)
+	truncate := r.common.Styles.Renderer.NewStyle().MaxWidth(r.common.Width)
 	header := r.selectedRepo.ProjectName()
 	if header == "" {
 		header = r.selectedRepo.Name()

pkg/ui/pages/selection/selection.go 🔗

@@ -51,7 +51,7 @@ func New(c common.Common) *Selection {
 		ts[i] = b.String()
 	}
 	t := tabs.New(c, ts)
-	t.TabSeparator = lipgloss.NewStyle()
+	t.TabSeparator = c.Styles.Renderer.NewStyle()
 	t.TabInactive = c.Styles.TopLevelNormalTab.Copy()
 	t.TabActive = c.Styles.TopLevelActiveTab.Copy()
 	t.TabDot = c.Styles.TopLevelActiveTabDot.Copy()
@@ -287,17 +287,18 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 func (s *Selection) View() string {
 	var view string
 	wm, hm := s.getMargins()
+	r := s.common.Styles.Renderer
 	switch s.activePane {
 	case selectorPane:
-		ss := lipgloss.NewStyle().
+		ss := r.NewStyle().
 			Width(s.common.Width - wm).
 			Height(s.common.Height - hm)
 		view = ss.Render(s.selector.View())
 	case readmePane:
-		rs := lipgloss.NewStyle().
+		rs := r.NewStyle().
 			Height(s.common.Height - hm)
 		status := fmt.Sprintf("☰ %.f%%", s.readme.ScrollPercent()*100)
-		readmeStatus := lipgloss.NewStyle().
+		readmeStatus := r.NewStyle().
 			Align(lipgloss.Right).
 			Width(s.common.Width - wm).
 			Foreground(s.common.Styles.InactiveBorderColor).

pkg/ui/styles/styles.go 🔗

@@ -162,24 +162,28 @@ type Styles struct {
 		LineDigit lipgloss.Style
 		LineBar   lipgloss.Style
 	}
+
+	// Deprecated: do not use this, migrate all styles into the Styles struct.
+	Renderer *lipgloss.Renderer
 }
 
 // DefaultStyles returns default styles for the UI.
-func DefaultStyles() *Styles {
+func DefaultStyles(r *lipgloss.Renderer) *Styles {
 	highlightColor := lipgloss.Color("210")
 	highlightColorDim := lipgloss.Color("174")
 	selectorColor := lipgloss.Color("167")
 	hashColor := lipgloss.Color("185")
 
 	s := new(Styles)
+	s.Renderer = r
 
 	s.ActiveBorderColor = lipgloss.Color("62")
 	s.InactiveBorderColor = lipgloss.Color("241")
 
-	s.App = lipgloss.NewStyle().
+	s.App = r.NewStyle().
 		Margin(1, 2)
 
-	s.ServerName = lipgloss.NewStyle().
+	s.ServerName = r.NewStyle().
 		Height(1).
 		MarginLeft(1).
 		MarginBottom(1).
@@ -188,29 +192,29 @@ func DefaultStyles() *Styles {
 		Foreground(lipgloss.Color("229")).
 		Bold(true)
 
-	s.TopLevelNormalTab = lipgloss.NewStyle().
+	s.TopLevelNormalTab = r.NewStyle().
 		MarginRight(2)
 
 	s.TopLevelActiveTab = s.TopLevelNormalTab.Copy().
 		Foreground(lipgloss.Color("36"))
 
-	s.TopLevelActiveTabDot = lipgloss.NewStyle().
+	s.TopLevelActiveTabDot = r.NewStyle().
 		Foreground(lipgloss.Color("36"))
 
-	s.RepoSelector.Normal.Base = lipgloss.NewStyle().
+	s.RepoSelector.Normal.Base = r.NewStyle().
 		PaddingLeft(1).
 		Border(lipgloss.Border{Left: " "}, false, false, false, true).
 		Height(3)
 
-	s.RepoSelector.Normal.Title = lipgloss.NewStyle().Bold(true)
+	s.RepoSelector.Normal.Title = r.NewStyle().Bold(true)
 
-	s.RepoSelector.Normal.Desc = lipgloss.NewStyle().
+	s.RepoSelector.Normal.Desc = r.NewStyle().
 		Foreground(lipgloss.Color("243"))
 
-	s.RepoSelector.Normal.Command = lipgloss.NewStyle().
+	s.RepoSelector.Normal.Command = r.NewStyle().
 		Foreground(lipgloss.Color("132"))
 
-	s.RepoSelector.Normal.Updated = lipgloss.NewStyle().
+	s.RepoSelector.Normal.Updated = r.NewStyle().
 		Foreground(lipgloss.Color("243"))
 
 	s.RepoSelector.Active.Base = s.RepoSelector.Normal.Base.Copy().
@@ -229,78 +233,78 @@ func DefaultStyles() *Styles {
 	s.RepoSelector.Active.Command = s.RepoSelector.Normal.Command.Copy().
 		Foreground(lipgloss.Color("204"))
 
-	s.MenuItem = lipgloss.NewStyle().
+	s.MenuItem = r.NewStyle().
 		PaddingLeft(1).
 		Border(lipgloss.Border{
 			Left: " ",
 		}, false, false, false, true).
 		Height(3)
 
-	s.MenuLastUpdate = lipgloss.NewStyle().
+	s.MenuLastUpdate = r.NewStyle().
 		Foreground(lipgloss.Color("241")).
 		Align(lipgloss.Right)
 
-	s.Repo.Base = lipgloss.NewStyle()
+	s.Repo.Base = r.NewStyle()
 
-	s.Repo.Title = lipgloss.NewStyle().
+	s.Repo.Title = r.NewStyle().
 		Padding(0, 2)
 
-	s.Repo.Command = lipgloss.NewStyle().
+	s.Repo.Command = r.NewStyle().
 		Foreground(lipgloss.Color("168"))
 
-	s.Repo.Body = lipgloss.NewStyle().
+	s.Repo.Body = r.NewStyle().
 		Margin(1, 0)
 
-	s.Repo.Header = lipgloss.NewStyle().
+	s.Repo.Header = r.NewStyle().
 		MaxHeight(2).
 		Border(lipgloss.NormalBorder(), false, false, true, false).
 		BorderForeground(lipgloss.Color("236"))
 
-	s.Repo.HeaderName = lipgloss.NewStyle().
+	s.Repo.HeaderName = r.NewStyle().
 		Foreground(lipgloss.Color("212")).
 		Bold(true)
 
-	s.Repo.HeaderDesc = lipgloss.NewStyle().
+	s.Repo.HeaderDesc = r.NewStyle().
 		Foreground(lipgloss.Color("243"))
 
-	s.Footer = lipgloss.NewStyle().
+	s.Footer = r.NewStyle().
 		MarginTop(1).
 		Padding(0, 1).
 		Height(1)
 
-	s.Branch = lipgloss.NewStyle().
+	s.Branch = r.NewStyle().
 		Foreground(lipgloss.Color("203")).
 		Background(lipgloss.Color("236")).
 		Padding(0, 1)
 
-	s.HelpKey = lipgloss.NewStyle().
+	s.HelpKey = r.NewStyle().
 		Foreground(lipgloss.Color("241"))
 
-	s.HelpValue = lipgloss.NewStyle().
+	s.HelpValue = r.NewStyle().
 		Foreground(lipgloss.Color("239"))
 
-	s.HelpDivider = lipgloss.NewStyle().
+	s.HelpDivider = r.NewStyle().
 		Foreground(lipgloss.Color("237")).
 		SetString(" • ")
 
-	s.URLStyle = lipgloss.NewStyle().
+	s.URLStyle = r.NewStyle().
 		MarginLeft(1).
 		Foreground(lipgloss.Color("168"))
 
-	s.Error = lipgloss.NewStyle().
+	s.Error = r.NewStyle().
 		MarginTop(2)
 
-	s.ErrorTitle = lipgloss.NewStyle().
+	s.ErrorTitle = r.NewStyle().
 		Foreground(lipgloss.Color("230")).
 		Background(lipgloss.Color("204")).
 		Bold(true).
 		Padding(0, 1)
 
-	s.ErrorBody = lipgloss.NewStyle().
+	s.ErrorBody = r.NewStyle().
 		Foreground(lipgloss.Color("252")).
 		MarginLeft(2)
 
-	s.LogItem.Normal.Base = lipgloss.NewStyle().
+	s.LogItem.Normal.Base = r.NewStyle().
 		Border(lipgloss.Border{
 			Left: " ",
 		}, false, false, false, true).
@@ -315,113 +319,113 @@ func DefaultStyles() *Styles {
 	s.LogItem.Active.Hash = s.LogItem.Normal.Hash.Copy().
 		Foreground(hashColor)
 
-	s.LogItem.Active.Hash = lipgloss.NewStyle().
+	s.LogItem.Active.Hash = r.NewStyle().
 		Bold(true).
 		Foreground(highlightColor)
 
-	s.LogItem.Normal.Title = lipgloss.NewStyle().
+	s.LogItem.Normal.Title = r.NewStyle().
 		Foreground(lipgloss.Color("105"))
 
-	s.LogItem.Active.Title = lipgloss.NewStyle().
+	s.LogItem.Active.Title = r.NewStyle().
 		Foreground(highlightColor).
 		Bold(true)
 
-	s.LogItem.Normal.Desc = lipgloss.NewStyle().
+	s.LogItem.Normal.Desc = r.NewStyle().
 		Foreground(lipgloss.Color("246"))
 
-	s.LogItem.Active.Desc = lipgloss.NewStyle().
+	s.LogItem.Active.Desc = r.NewStyle().
 		Foreground(lipgloss.Color("95"))
 
 	s.LogItem.Active.Keyword = s.LogItem.Active.Desc.Copy().
 		Foreground(highlightColorDim)
 
-	s.LogItem.Normal.Hash = lipgloss.NewStyle().
+	s.LogItem.Normal.Hash = r.NewStyle().
 		Foreground(hashColor)
 
-	s.LogItem.Active.Hash = lipgloss.NewStyle().
+	s.LogItem.Active.Hash = r.NewStyle().
 		Foreground(highlightColor)
 
-	s.Log.Commit = lipgloss.NewStyle().
+	s.Log.Commit = r.NewStyle().
 		Margin(0, 2)
 
-	s.Log.CommitHash = lipgloss.NewStyle().
+	s.Log.CommitHash = r.NewStyle().
 		Foreground(hashColor).
 		Bold(true)
 
-	s.Log.CommitBody = lipgloss.NewStyle().
+	s.Log.CommitBody = r.NewStyle().
 		MarginTop(1).
 		MarginLeft(2)
 
-	s.Log.CommitStatsAdd = lipgloss.NewStyle().
+	s.Log.CommitStatsAdd = r.NewStyle().
 		Foreground(lipgloss.Color("42")).
 		Bold(true)
 
-	s.Log.CommitStatsDel = lipgloss.NewStyle().
+	s.Log.CommitStatsDel = r.NewStyle().
 		Foreground(lipgloss.Color("203")).
 		Bold(true)
 
-	s.Log.Paginator = lipgloss.NewStyle().
+	s.Log.Paginator = r.NewStyle().
 		Margin(0).
 		Align(lipgloss.Center)
 
-	s.Ref.Normal.Item = lipgloss.NewStyle()
+	s.Ref.Normal.Item = r.NewStyle()
 
-	s.Ref.ItemSelector = lipgloss.NewStyle().
+	s.Ref.ItemSelector = r.NewStyle().
 		Foreground(selectorColor).
 		SetString("> ")
 
-	s.Ref.Active.Item = lipgloss.NewStyle().
+	s.Ref.Active.Item = r.NewStyle().
 		Foreground(highlightColorDim)
 
-	s.Ref.Normal.Base = lipgloss.NewStyle()
+	s.Ref.Normal.Base = r.NewStyle()
 
-	s.Ref.Active.Base = lipgloss.NewStyle()
+	s.Ref.Active.Base = r.NewStyle()
 
-	s.Ref.Normal.ItemTag = lipgloss.NewStyle().
+	s.Ref.Normal.ItemTag = r.NewStyle().
 		Foreground(lipgloss.Color("39"))
 
-	s.Ref.Active.ItemTag = lipgloss.NewStyle().
+	s.Ref.Active.ItemTag = r.NewStyle().
 		Bold(true).
 		Foreground(highlightColor)
 
-	s.Ref.Active.Item = lipgloss.NewStyle().
+	s.Ref.Active.Item = r.NewStyle().
 		Bold(true).
 		Foreground(highlightColor)
 
-	s.Ref.Normal.ItemDesc = lipgloss.NewStyle().
+	s.Ref.Normal.ItemDesc = r.NewStyle().
 		Faint(true)
 
-	s.Ref.Active.ItemDesc = lipgloss.NewStyle().
+	s.Ref.Active.ItemDesc = r.NewStyle().
 		Foreground(highlightColor).
 		Faint(true)
 
-	s.Ref.Normal.ItemHash = lipgloss.NewStyle().
+	s.Ref.Normal.ItemHash = r.NewStyle().
 		Foreground(hashColor).
 		Bold(true)
 
-	s.Ref.Active.ItemHash = lipgloss.NewStyle().
+	s.Ref.Active.ItemHash = r.NewStyle().
 		Foreground(highlightColor).
 		Bold(true)
 
 	s.Ref.Paginator = s.Log.Paginator.Copy()
 
-	s.Ref.Selector = lipgloss.NewStyle()
+	s.Ref.Selector = r.NewStyle()
 
 	s.Tree.Selector = s.Tree.Normal.FileName.Copy().
 		Width(1).
 		Foreground(selectorColor)
 
-	s.Tree.Normal.FileName = lipgloss.NewStyle().
+	s.Tree.Normal.FileName = r.NewStyle().
 		MarginLeft(1)
 
 	s.Tree.Active.FileName = s.Tree.Normal.FileName.Copy().
 		Bold(true).
 		Foreground(highlightColor)
 
-	s.Tree.Normal.FileDir = lipgloss.NewStyle().
+	s.Tree.Normal.FileDir = r.NewStyle().
 		Foreground(lipgloss.Color("39"))
 
-	s.Tree.Active.FileDir = lipgloss.NewStyle().
+	s.Tree.Active.FileDir = r.NewStyle().
 		Foreground(highlightColor)
 
 	s.Tree.Normal.FileMode = s.Tree.Active.FileName.Copy().
@@ -437,87 +441,87 @@ func DefaultStyles() *Styles {
 	s.Tree.Active.FileSize = s.Tree.Normal.FileName.Copy().
 		Foreground(highlightColorDim)
 
-	s.Tree.FileContent = lipgloss.NewStyle()
+	s.Tree.FileContent = r.NewStyle()
 
 	s.Tree.Paginator = s.Log.Paginator.Copy()
 
-	s.Tree.Blame.Hash = lipgloss.NewStyle().
+	s.Tree.Blame.Hash = r.NewStyle().
 		Foreground(hashColor).
 		Bold(true)
 
-	s.Tree.Blame.Message = lipgloss.NewStyle()
+	s.Tree.Blame.Message = r.NewStyle()
 
-	s.Tree.Blame.Who = lipgloss.NewStyle().
+	s.Tree.Blame.Who = r.NewStyle().
 		Faint(true)
 
-	s.Spinner = lipgloss.NewStyle().
+	s.Spinner = r.NewStyle().
 		MarginTop(1).
 		MarginLeft(2).
 		Foreground(lipgloss.Color("205"))
 
-	s.SpinnerContainer = lipgloss.NewStyle()
+	s.SpinnerContainer = r.NewStyle()
 
-	s.NoContent = lipgloss.NewStyle().
+	s.NoContent = r.NewStyle().
 		MarginTop(1).
 		MarginLeft(2).
 		Foreground(lipgloss.Color("242"))
 
-	s.StatusBar = lipgloss.NewStyle().
+	s.StatusBar = r.NewStyle().
 		Height(1)
 
-	s.StatusBarKey = lipgloss.NewStyle().
+	s.StatusBarKey = r.NewStyle().
 		Bold(true).
 		Padding(0, 1).
 		Background(lipgloss.Color("206")).
 		Foreground(lipgloss.Color("228"))
 
-	s.StatusBarValue = lipgloss.NewStyle().
+	s.StatusBarValue = r.NewStyle().
 		Padding(0, 1).
 		Background(lipgloss.Color("235")).
 		Foreground(lipgloss.Color("243"))
 
-	s.StatusBarInfo = lipgloss.NewStyle().
+	s.StatusBarInfo = r.NewStyle().
 		Padding(0, 1).
 		Background(lipgloss.Color("212")).
 		Foreground(lipgloss.Color("230"))
 
-	s.StatusBarBranch = lipgloss.NewStyle().
+	s.StatusBarBranch = r.NewStyle().
 		Padding(0, 1).
 		Background(lipgloss.Color("62")).
 		Foreground(lipgloss.Color("230"))
 
-	s.StatusBarHelp = lipgloss.NewStyle().
+	s.StatusBarHelp = r.NewStyle().
 		Padding(0, 1).
 		Background(lipgloss.Color("237")).
 		Foreground(lipgloss.Color("243"))
 
-	s.Tabs = lipgloss.NewStyle().
+	s.Tabs = r.NewStyle().
 		Height(1)
 
-	s.TabInactive = lipgloss.NewStyle()
+	s.TabInactive = r.NewStyle()
 
-	s.TabActive = lipgloss.NewStyle().
+	s.TabActive = r.NewStyle().
 		Underline(true).
 		Foreground(lipgloss.Color("36"))
 
-	s.TabSeparator = lipgloss.NewStyle().
+	s.TabSeparator = r.NewStyle().
 		SetString("│").
 		Padding(0, 1).
 		Foreground(lipgloss.Color("238"))
 
-	s.Code.LineDigit = lipgloss.NewStyle().Foreground(lipgloss.Color("239"))
+	s.Code.LineDigit = r.NewStyle().Foreground(lipgloss.Color("239"))
 
-	s.Code.LineBar = lipgloss.NewStyle().Foreground(lipgloss.Color("236"))
+	s.Code.LineBar = r.NewStyle().Foreground(lipgloss.Color("236"))
 
-	s.Stash.Normal.Message = lipgloss.NewStyle().MarginLeft(1)
+	s.Stash.Normal.Message = r.NewStyle().MarginLeft(1)
 
 	s.Stash.Active.Message = s.Stash.Normal.Message.Copy().Foreground(selectorColor)
 
-	s.Stash.Title = lipgloss.NewStyle().
+	s.Stash.Title = r.NewStyle().
 		Foreground(hashColor).
 		Bold(true)
 
-	s.Stash.Selector = lipgloss.NewStyle().
+	s.Stash.Selector = r.NewStyle().
 		Width(1).
 		Foreground(selectorColor)