From 9503c88032188deae24cbc94456c1edd3f61fa02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 13:59:03 -0400 Subject: [PATCH 01/12] feat(deps): bump github.com/charmbracelet/lipgloss from 0.8.0 to 0.9.1 (#401) Bumps [github.com/charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) from 0.8.0 to 0.9.1. - [Release notes](https://github.com/charmbracelet/lipgloss/releases) - [Commits](https://github.com/charmbracelet/lipgloss/compare/v0.8.0...v0.9.1) --- updated-dependencies: - dependency-name: github.com/charmbracelet/lipgloss dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index e4168a68e..5e35793b6 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/charmbracelet/bubbles v0.16.1 github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/glamour v0.6.0 - github.com/charmbracelet/lipgloss v0.8.0 + github.com/charmbracelet/lipgloss v0.9.1 github.com/charmbracelet/wish v1.1.1 github.com/dustin/go-humanize v1.0.1 github.com/go-git/go-git/v5 v5.9.0 @@ -71,7 +71,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75 // indirect github.com/microcosm-cc/bluemonday v1.0.21 // indirect diff --git a/go.sum b/go.sum index 4e7cb946a..5b504c3ef 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM2 github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= github.com/charmbracelet/keygen v0.4.3 h1:ywOZRwkDlpmkawl0BgLTxaYWDSqp6Y4nfVVmgyyO1Mg= github.com/charmbracelet/keygen v0.4.3/go.mod h1:4e4FT3HSdLU/u83RfJWvzJIaVb8aX4MxtDlfXwpDJaI= -github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= -github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc= github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k= github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09 h1:ZDIQmTtohv0S/AAYE//w8mYTxCzqphhF1+4ACPDMiLU= @@ -115,8 +115,9 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= From da50842912065fbf3002dc658600efaaf11354ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:13:30 -0400 Subject: [PATCH 02/12] feat(deps): bump github.com/charmbracelet/keygen from 0.4.3 to 0.5.0 (#398) Bumps [github.com/charmbracelet/keygen](https://github.com/charmbracelet/keygen) from 0.4.3 to 0.5.0. - [Release notes](https://github.com/charmbracelet/keygen/releases) - [Commits](https://github.com/charmbracelet/keygen/compare/v0.4.3...v0.5.0) --- updated-dependencies: - dependency-name: github.com/charmbracelet/keygen dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 ++++----- go.sum | 18 ++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 5e35793b6..f2c250215 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/caarlos0/env/v8 v8.0.0 github.com/caarlos0/tablewriter v0.1.0 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245 - github.com/charmbracelet/keygen v0.4.3 + github.com/charmbracelet/keygen v0.5.0 github.com/charmbracelet/log v0.2.5 github.com/charmbracelet/ssh v0.0.0-20230822194956-1a051f898e09 github.com/go-jose/go-jose/v3 v3.0.0 @@ -43,7 +43,7 @@ require ( github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 github.com/spf13/cobra v1.7.0 go.uber.org/automaxprocs v1.5.3 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/sync v0.3.0 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.26.0 @@ -55,7 +55,6 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/dlclark/regexp2 v1.4.0 // indirect @@ -91,8 +90,8 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect diff --git a/go.sum b/go.sum index 5b504c3ef..a98813797 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,6 @@ github.com/caarlos0/duration v0.0.0-20220103233809-8df7c22fe305 h1:vJpZ14MU1/Yhq github.com/caarlos0/duration v0.0.0-20220103233809-8df7c22fe305/go.mod h1:mSkwb/eZEwOJJJ4tqAKiuhLIPe0e9+FKhlU0oMCpbf8= github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0= github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo= -github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= -github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/tablewriter v0.1.0 h1:HWwl/Zh3GKgVejSeG8lKHc28YBbI7bLRW2tgvxFF2DA= github.com/caarlos0/tablewriter v0.1.0/go.mod h1:oZ3/mQeP+SC5c1Dr6zv/6jCf0dfsUWq+PuwNw8l3ir0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -29,8 +27,8 @@ github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245 h github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20230725143853-5dd0632f9245/go.mod h1:eXJuVicxnjRgRMokmutZdistxoMRjBjjfqvrYq7bCIU= github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= -github.com/charmbracelet/keygen v0.4.3 h1:ywOZRwkDlpmkawl0BgLTxaYWDSqp6Y4nfVVmgyyO1Mg= -github.com/charmbracelet/keygen v0.4.3/go.mod h1:4e4FT3HSdLU/u83RfJWvzJIaVb8aX4MxtDlfXwpDJaI= +github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc= +github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc= @@ -201,8 +199,8 @@ go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnw 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.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -223,13 +221,13 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc 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.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 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= From 6662e1ae3071099c49f98a42d628f286e26ab649 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:51:08 -0700 Subject: [PATCH 03/12] feat(deps): bump golang.org/x/net from 0.15.0 to 0.17.0 (#400) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f2c250215..92bc01ac5 100644 --- a/go.mod +++ b/go.mod @@ -89,7 +89,7 @@ require ( github.com/yuin/goldmark v1.5.2 // indirect github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect diff --git a/go.sum b/go.sum index a98813797..483ff2a33 100644 --- a/go.sum +++ b/go.sum @@ -206,8 +206,8 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= From 5df79ee29192151ffff5658336bcd463aafdbc70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:29:39 -0700 Subject: [PATCH 04/12] feat(deps): bump golang.org/x/sync from 0.3.0 to 0.4.0 (#395) --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 92bc01ac5..dfd99002a 100644 --- a/go.mod +++ b/go.mod @@ -44,7 +44,7 @@ require ( github.com/spf13/cobra v1.7.0 go.uber.org/automaxprocs v1.5.3 golang.org/x/crypto v0.14.0 - golang.org/x/sync v0.3.0 + golang.org/x/sync v0.4.0 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.26.0 ) diff --git a/go.sum b/go.sum index 483ff2a33..37e2c33b4 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,9 @@ golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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= From 4028482a8af599c675da8b42f7a866281cbbbc21 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 21 Aug 2023 16:48:54 -0400 Subject: [PATCH 05/12] feat: browse local repositories --- cmd/soft/browse.go | 248 ++++++++++++++++++++++++++++++++++++++ server/ssh/session.go | 3 +- server/{ui => ssh}/ui.go | 6 +- server/ui/common/utils.go | 2 +- 4 files changed, 253 insertions(+), 6 deletions(-) create mode 100644 cmd/soft/browse.go rename server/{ui => ssh}/ui.go (98%) diff --git a/cmd/soft/browse.go b/cmd/soft/browse.go new file mode 100644 index 000000000..223914235 --- /dev/null +++ b/cmd/soft/browse.go @@ -0,0 +1,248 @@ +package main + +import ( + "io" + "os" + "path/filepath" + "time" + + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/soft-serve/git" + "github.com/charmbracelet/soft-serve/server/proto" + "github.com/charmbracelet/soft-serve/server/ui/common" + "github.com/charmbracelet/soft-serve/server/ui/components/footer" + "github.com/charmbracelet/soft-serve/server/ui/pages/repo" + "github.com/muesli/termenv" + "github.com/spf13/cobra" +) + +var browseCmd = &cobra.Command{ + Use: "browse PATH", + Short: "Browse a repository", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + rp := "." + if len(args) > 0 { + rp = args[0] + } + + abs, err := filepath.Abs(rp) + if err != nil { + return err + } + + rp = abs + + r, err := git.Open(rp) + if err != nil { + return err + } + + // Bubble Tea uses Termenv default output so we have to use the same + // thing here. + output := termenv.DefaultOutput() + ctx := cmd.Context() + c := common.NewCommon(ctx, output, 0, 0) + m := &model{ + m: repo.New(c), + r: r, + c: c, + } + + m.f = footer.New(c, m) + p := tea.NewProgram(m, + tea.WithAltScreen(), + tea.WithMouseCellMotion(), + ) + + _, err = p.Run() + return err + }, +} + +func init() { + // HACK: This is a hack to hide the clone url + // TODO: Make this configurable + common.CloneCmd = func(publicURL, name string) string { return "" } + rootCmd.AddCommand(browseCmd) +} + +type model struct { + m *repo.Repo + f *footer.Footer + r *git.Repository + c common.Common + showFooter bool +} + +var _ tea.Model = &model{} + +func (m model) repo() proto.Repository { + return repository{r: m.r} +} + +func (m model) repoCmd() tea.Msg { + return repo.RepoMsg(m.repo()) +} + +func (m *model) SetSize(w, h int) { + m.c.SetSize(w, h) + style := m.c.Styles.App.Copy() + wm := style.GetHorizontalFrameSize() + hm := style.GetVerticalFrameSize() + if m.showFooter { + hm += m.f.Height() + } + + m.f.SetSize(w-wm, h-hm) + m.m.SetSize(w-wm, h-hm) +} + +// ShortHelp implements help.KeyMap. +func (m model) ShortHelp() []key.Binding { + return m.m.ShortHelp() +} + +// FullHelp implements help.KeyMap. +func (m model) FullHelp() [][]key.Binding { + return m.m.FullHelp() +} + +// Init implements tea.Model. +func (m *model) Init() tea.Cmd { + return tea.Batch( + m.m.Init(), + m.f.Init(), + m.repoCmd, + repo.UpdateRefCmd(m.repo()), + ) +} + +// Update implements tea.Model. +func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + cmds := make([]tea.Cmd, 0) + switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch { + case key.Matches(msg, m.c.KeyMap.Help): + cmds = append(cmds, footer.ToggleFooterCmd) + case key.Matches(msg, m.c.KeyMap.Quit): + return m, tea.Quit + } + case tea.MouseMsg: + switch msg.Type { + case tea.MouseLeft: + switch { + case m.c.Zone.Get("footer").InBounds(msg): + cmds = append(cmds, footer.ToggleFooterCmd) + } + } + case footer.ToggleFooterMsg: + m.f.SetShowAll(!m.f.ShowAll()) + m.showFooter = !m.showFooter + } + + f, cmd := m.f.Update(msg) + m.f = f.(*footer.Footer) + if cmd != nil { + cmds = append(cmds, cmd) + } + + r, cmd := m.m.Update(msg) + m.m = r.(*repo.Repo) + if cmd != nil { + cmds = append(cmds, cmd) + } + + // This fixes determining the height margin of the footer. + m.SetSize(m.c.Width, m.c.Height) + + return m, tea.Batch(cmds...) +} + +// View implements tea.Model. +func (m *model) View() string { + view := m.m.View() + if m.showFooter { + view = lipgloss.JoinVertical(lipgloss.Left, view, m.f.View()) + } + + return m.c.Zone.Scan(m.c.Styles.App.Render(view)) +} + +type repository struct { + r *git.Repository +} + +var _ proto.Repository = repository{} + +// Description implements proto.Repository. +func (r repository) Description() string { + fp := filepath.Join(r.r.Path, "description") + f, err := os.Open(fp) + if err != nil { + return "" + } + + defer f.Close() // nolint: errcheck + bts, err := io.ReadAll(f) + if err != nil { + return "" + } + + return string(bts) +} + +// ID implements proto.Repository. +func (r repository) ID() int64 { + return 0 +} + +// IsHidden implements proto.Repository. +func (repository) IsHidden() bool { + return false +} + +// IsMirror implements proto.Repository. +func (repository) IsMirror() bool { + return false +} + +// IsPrivate implements proto.Repository. +func (repository) IsPrivate() bool { + return false +} + +// Name implements proto.Repository. +func (r repository) Name() string { + return filepath.Base(r.r.Path) +} + +// Open implements proto.Repository. +func (r repository) Open() (*git.Repository, error) { + return r.r, nil +} + +// ProjectName implements proto.Repository. +func (r repository) ProjectName() string { + return r.Name() +} + +// UpdatedAt implements proto.Repository. +func (r repository) UpdatedAt() time.Time { + t, err := r.r.LatestCommitTime() + if err != nil { + return time.Time{} + } + + return t +} + +// UserID implements proto.Repository. +func (r repository) UserID() int64 { + return 0 +} diff --git a/server/ssh/session.go b/server/ssh/session.go index a5bd4d168..53496e3af 100644 --- a/server/ssh/session.go +++ b/server/ssh/session.go @@ -9,7 +9,6 @@ import ( "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/config" "github.com/charmbracelet/soft-serve/server/proto" - "github.com/charmbracelet/soft-serve/server/ui" "github.com/charmbracelet/soft-serve/server/ui/common" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" @@ -58,7 +57,7 @@ func SessionHandler(s ssh.Session) *tea.Program { output := termenv.NewOutput(s, termenv.WithColorCache(true), termenv.WithEnvironment(envs)) c := common.NewCommon(ctx, output, pty.Window.Width, pty.Window.Height) c.SetValue(common.ConfigKey, cfg) - m := ui.New(c, initialRepo) + m := NewUI(c, initialRepo) p := tea.NewProgram(m, tea.WithInput(s), tea.WithOutput(s), diff --git a/server/ui/ui.go b/server/ssh/ui.go similarity index 98% rename from server/ui/ui.go rename to server/ssh/ui.go index 5e259004d..ee4ca7ad7 100644 --- a/server/ui/ui.go +++ b/server/ssh/ui.go @@ -1,4 +1,4 @@ -package ui +package ssh import ( "errors" @@ -45,8 +45,8 @@ type UI struct { error error } -// New returns a new UI model. -func New(c common.Common, initialRepo string) *UI { +// NewUI returns a new UI model. +func NewUI(c common.Common, initialRepo string) *UI { serverName := c.Config().Name h := header.New(c, serverName) ui := &UI{ diff --git a/server/ui/common/utils.go b/server/ui/common/utils.go index aac2db6da..49eaf00ce 100644 --- a/server/ui/common/utils.go +++ b/server/ui/common/utils.go @@ -35,6 +35,6 @@ func RepoURL(publicURL, name string) string { } // CloneCmd returns the URL of the repository. -func CloneCmd(publicURL, name string) string { +var CloneCmd = func(publicURL, name string) string { return fmt.Sprintf("git clone %s", RepoURL(publicURL, name)) } From b5eccb349e94b8655a19be2f3a4d3771ea1af86b Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 5 Oct 2023 14:48:08 -0400 Subject: [PATCH 06/12] refactor(ui): resolve race conditions and clean up code --- cmd/soft/browse.go | 110 +++++++-- server/ssh/ui.go | 9 +- server/ui/common/component.go | 18 ++ server/ui/components/selector/selector.go | 141 ++++++++--- server/ui/components/statusbar/statusbar.go | 22 +- server/ui/pages/repo/files.go | 125 +++++----- server/ui/pages/repo/log.go | 120 +++++----- server/ui/pages/repo/readme.go | 54 ++++- server/ui/pages/repo/refs.go | 44 +++- server/ui/pages/repo/repo.go | 252 ++++++++------------ 10 files changed, 542 insertions(+), 353 deletions(-) diff --git a/cmd/soft/browse.go b/cmd/soft/browse.go index 223914235..9b346b417 100644 --- a/cmd/soft/browse.go +++ b/cmd/soft/browse.go @@ -33,22 +33,21 @@ var browseCmd = &cobra.Command{ return err } - rp = abs - - r, err := git.Open(rp) - if err != nil { - return err - } - // Bubble Tea uses Termenv default output so we have to use the same // thing here. output := termenv.DefaultOutput() ctx := cmd.Context() c := common.NewCommon(ctx, output, 0, 0) m := &model{ - m: repo.New(c), - r: r, - c: c, + m: repo.New(c, + repo.NewReadme(c), + repo.NewFiles(c), + repo.NewLog(c), + repo.NewRefs(c, git.RefsHeads), + repo.NewRefs(c, git.RefsTags), + ), + repoPath: abs, + c: c, } m.f = footer.New(c, m) @@ -69,24 +68,25 @@ func init() { rootCmd.AddCommand(browseCmd) } +type state int + +const ( + startState state = iota + errorState +) + type model struct { m *repo.Repo f *footer.Footer - r *git.Repository + repoPath string c common.Common + state state showFooter bool + error error } var _ tea.Model = &model{} -func (m model) repo() proto.Repository { - return repository{r: m.r} -} - -func (m model) repoCmd() tea.Msg { - return repo.RepoMsg(m.repo()) -} - func (m *model) SetSize(w, h int) { m.c.SetSize(w, h) style := m.c.Styles.App.Copy() @@ -102,35 +102,73 @@ func (m *model) SetSize(w, h int) { // ShortHelp implements help.KeyMap. func (m model) ShortHelp() []key.Binding { - return m.m.ShortHelp() + switch m.state { + case errorState: + return []key.Binding{ + m.c.KeyMap.Back, + m.c.KeyMap.Quit, + m.c.KeyMap.Help, + } + default: + return m.m.ShortHelp() + } } // FullHelp implements help.KeyMap. func (m model) FullHelp() [][]key.Binding { - return m.m.FullHelp() + switch m.state { + case errorState: + return [][]key.Binding{ + { + m.c.KeyMap.Back, + }, + { + m.c.KeyMap.Quit, + m.c.KeyMap.Help, + }, + } + default: + return m.m.FullHelp() + } } // Init implements tea.Model. func (m *model) Init() tea.Cmd { + rr, err := git.Open(m.repoPath) + if err != nil { + return common.ErrorCmd(err) + } + + r := repository{rr} return tea.Batch( m.m.Init(), m.f.Init(), - m.repoCmd, - repo.UpdateRefCmd(m.repo()), + func() tea.Msg { + return repo.RepoMsg(r) + }, + repo.UpdateRefCmd(r), ) } // Update implements tea.Model. func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + m.c.Logger.Debugf("msg received: %T", msg) cmds := make([]tea.Cmd, 0) switch msg := msg.(type) { case tea.WindowSizeMsg: m.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { + case key.Matches(msg, m.c.KeyMap.Back) && m.error != nil: + m.error = nil + m.state = startState + // Always show the footer on error. + m.showFooter = m.f.ShowAll() case key.Matches(msg, m.c.KeyMap.Help): cmds = append(cmds, footer.ToggleFooterCmd) case key.Matches(msg, m.c.KeyMap.Quit): + // Stop bubblezone background workers. + m.c.Zone.Close() return m, tea.Quit } case tea.MouseMsg: @@ -144,6 +182,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case footer.ToggleFooterMsg: m.f.SetShowAll(!m.f.ShowAll()) m.showFooter = !m.showFooter + case common.ErrorMsg: + m.error = msg + m.state = errorState + m.showFooter = true } f, cmd := m.f.Update(msg) @@ -171,7 +213,27 @@ func (m *model) View() string { view = lipgloss.JoinVertical(lipgloss.Left, view, m.f.View()) } - return m.c.Zone.Scan(m.c.Styles.App.Render(view)) + switch m.state { + case errorState: + appStyle := m.c.Styles.App.Copy() + wm, hm := appStyle.GetHorizontalFrameSize(), appStyle.GetVerticalFrameSize() + if m.showFooter { + hm += m.f.Height() + } + + err := m.c.Styles.ErrorTitle.Render("Bummer") + err += m.c.Styles.ErrorBody.Render(m.error.Error()) + return m.c.Styles.Error.Copy(). + Width(m.c.Width - + wm - + m.c.Styles.ErrorBody.GetHorizontalFrameSize()). + Height(m.c.Height - + hm - + m.c.Styles.Error.GetVerticalFrameSize()). + Render(err) + default: + return m.c.Zone.Scan(m.c.Styles.App.Render(view)) + } } type repository struct { diff --git a/server/ssh/ui.go b/server/ssh/ui.go index ee4ca7ad7..329e95c8d 100644 --- a/server/ssh/ui.go +++ b/server/ssh/ui.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/proto" "github.com/charmbracelet/soft-serve/server/ui/common" "github.com/charmbracelet/soft-serve/server/ui/components/footer" @@ -133,7 +134,13 @@ func (ui *UI) SetSize(width, height int) { // Init implements tea.Model. func (ui *UI) Init() tea.Cmd { ui.pages[selectionPage] = selection.New(ui.common) - ui.pages[repoPage] = repo.New(ui.common) + ui.pages[repoPage] = repo.New(ui.common, + repo.NewReadme(ui.common), + repo.NewFiles(ui.common), + repo.NewLog(ui.common), + repo.NewRefs(ui.common, git.RefsHeads), + repo.NewRefs(ui.common, git.RefsTags), + ) ui.SetSize(ui.common.Width, ui.common.Height) cmds := make([]tea.Cmd, 0) cmds = append(cmds, diff --git a/server/ui/common/component.go b/server/ui/common/component.go index ed8b9bf05..1f4d20df8 100644 --- a/server/ui/common/component.go +++ b/server/ui/common/component.go @@ -11,3 +11,21 @@ type Component interface { help.KeyMap SetSize(width, height int) } + +// TabComponenet represents a model that is mounted to a tab. +// TODO: find a better name +type TabComponent interface { + Component + + // StatusBarValue returns the status bar value component. + StatusBarValue() string + + // StatusBarInfo returns the status bar info component. + StatusBarInfo() string + + // SpinnerID returns the ID of the spinner. + SpinnerID() int + + // TabName returns the name of the tab. + TabName() string +} diff --git a/server/ui/components/selector/selector.go b/server/ui/components/selector/selector.go index aaf3191f3..f55c7a6c6 100644 --- a/server/ui/components/selector/selector.go +++ b/server/ui/components/selector/selector.go @@ -1,6 +1,8 @@ package selector import ( + "sync" + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" @@ -9,10 +11,16 @@ import ( // Selector is a list of items that can be selected. type Selector struct { - list.Model + *list.Model common common.Common active int filterState list.FilterState + + // XXX: we use a mutex to support concurrent access to the model. This is + // needed to implement pagination for the Log component. list.Model does + // not support item pagination so we hack it ourselves on top of + // list.Model. + mtx sync.RWMutex } // IdentifiableItem is an item that can be identified by a string. Implements @@ -42,7 +50,7 @@ func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) l := list.New(itms, delegate, common.Width, common.Height) l.Styles.NoItems = common.Styles.NoItems s := &Selector{ - Model: l, + Model: &l, common: common, } s.SetSize(common.Width, common.Height) @@ -51,66 +59,111 @@ func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) // PerPage returns the number of items per page. func (s *Selector) PerPage() int { + s.mtx.RLock() + defer s.mtx.RUnlock() return s.Model.Paginator.PerPage } // SetPage sets the current page. func (s *Selector) SetPage(page int) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.Paginator.Page = page } // Page returns the current page. func (s *Selector) Page() int { + s.mtx.RLock() + defer s.mtx.RUnlock() return s.Model.Paginator.Page } // TotalPages returns the total number of pages. func (s *Selector) TotalPages() int { + s.mtx.RLock() + defer s.mtx.RUnlock() return s.Model.Paginator.TotalPages } +// SetTotalPages sets the total number of pages given the number of items. +func (s *Selector) SetTotalPages(items int) int { + s.mtx.Lock() + defer s.mtx.Unlock() + return s.Model.Paginator.SetTotalPages(items) +} + +// SelectedItem returns the currently selected item. +func (s *Selector) SelectedItem() IdentifiableItem { + s.mtx.RLock() + defer s.mtx.RUnlock() + item := s.Model.SelectedItem() + i, ok := item.(IdentifiableItem) + if !ok { + return nil + } + return i +} + // Select selects the item at the given index. func (s *Selector) Select(index int) { + s.mtx.RLock() + defer s.mtx.RUnlock() s.Model.Select(index) } // SetShowTitle sets the show title flag. func (s *Selector) SetShowTitle(show bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetShowTitle(show) } // SetShowHelp sets the show help flag. func (s *Selector) SetShowHelp(show bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetShowHelp(show) } // SetShowStatusBar sets the show status bar flag. func (s *Selector) SetShowStatusBar(show bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetShowStatusBar(show) } // DisableQuitKeybindings disables the quit keybindings. func (s *Selector) DisableQuitKeybindings() { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.DisableQuitKeybindings() } // SetShowFilter sets the show filter flag. func (s *Selector) SetShowFilter(show bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetShowFilter(show) } // SetShowPagination sets the show pagination flag. func (s *Selector) SetShowPagination(show bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetShowPagination(show) } // SetFilteringEnabled sets the filtering enabled flag. func (s *Selector) SetFilteringEnabled(enabled bool) { + s.mtx.Lock() + defer s.mtx.Unlock() s.Model.SetFilteringEnabled(enabled) } // SetSize implements common.Component. func (s *Selector) SetSize(width, height int) { + s.mtx.Lock() + defer s.mtx.Unlock() s.common.SetSize(width, height) s.Model.SetSize(width, height) } @@ -121,14 +174,53 @@ func (s *Selector) SetItems(items []IdentifiableItem) tea.Cmd { for i, item := range items { its[i] = item } + s.mtx.Lock() + defer s.mtx.Unlock() return s.Model.SetItems(its) } // Index returns the index of the selected item. func (s *Selector) Index() int { + s.mtx.RLock() + defer s.mtx.RUnlock() return s.Model.Index() } +// Items returns the items in the selector. +func (s *Selector) Items() []list.Item { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.Model.Items() +} + +// VisibleItems returns all the visible items in the selector. +func (s *Selector) VisibleItems() []list.Item { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.Model.VisibleItems() +} + +// FilterState returns the filter state. +func (s *Selector) FilterState() list.FilterState { + s.mtx.RLock() + defer s.mtx.RUnlock() + return s.Model.FilterState() +} + +// CursorUp moves the cursor up. +func (s *Selector) CursorUp() { + s.mtx.Lock() + defer s.mtx.Unlock() + s.Model.CursorUp() +} + +// CursorDown moves the cursor down. +func (s *Selector) CursorDown() { + s.mtx.Lock() + defer s.mtx.Unlock() + s.Model.CursorDown() +} + // Init implements tea.Model. func (s *Selector) Init() tea.Cmd { return s.activeCmd @@ -141,26 +233,26 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.MouseMsg: switch msg.Type { case tea.MouseWheelUp: - s.Model.CursorUp() + s.CursorUp() case tea.MouseWheelDown: - s.Model.CursorDown() + s.CursorDown() case tea.MouseLeft: - curIdx := s.Model.Index() - for i, item := range s.Model.Items() { + curIdx := s.Index() + for i, item := range s.Items() { item, _ := item.(IdentifiableItem) // Check each item to see if it's in bounds. if item != nil && s.common.Zone.Get(item.ID()).InBounds(msg) { if i == curIdx { - cmds = append(cmds, s.selectCmd) + cmds = append(cmds, s.SelectItemCmd) } else { - s.Model.Select(i) + s.Select(i) } break } } } case tea.KeyMsg: - filterState := s.Model.FilterState() + filterState := s.FilterState() switch { case key.Matches(msg, s.common.KeyMap.Help): if filterState == list.Filtering { @@ -168,28 +260,30 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case key.Matches(msg, s.common.KeyMap.Select): if filterState != list.Filtering { - cmds = append(cmds, s.selectCmd) + cmds = append(cmds, s.SelectItemCmd) } } case list.FilterMatchesMsg: cmds = append(cmds, s.activeFilterCmd) } m, cmd := s.Model.Update(msg) - s.Model = m + s.mtx.Lock() + s.Model = &m + s.mtx.Unlock() if cmd != nil { cmds = append(cmds, cmd) } // Track filter state and update active item when filter state changes. - filterState := s.Model.FilterState() + filterState := s.FilterState() if s.filterState != filterState { cmds = append(cmds, s.activeFilterCmd) } s.filterState = filterState // Send ActiveMsg when index change. - if s.active != s.Model.Index() { + if s.active != s.Index() { cmds = append(cmds, s.activeCmd) } - s.active = s.Model.Index() + s.active = s.Index() return s, tea.Batch(cmds...) } @@ -198,22 +292,13 @@ func (s *Selector) View() string { return s.Model.View() } -// SelectItem is a command that selects the currently active item. -func (s *Selector) SelectItem() tea.Msg { - return s.selectCmd() -} - -func (s *Selector) selectCmd() tea.Msg { - item := s.Model.SelectedItem() - i, ok := item.(IdentifiableItem) - if !ok { - return SelectMsg{} - } - return SelectMsg{i} +// SelectItemCmd is a command that selects the currently active item. +func (s *Selector) SelectItemCmd() tea.Msg { + return SelectMsg{s.SelectedItem()} } func (s *Selector) activeCmd() tea.Msg { - item := s.Model.SelectedItem() + item := s.SelectedItem() i, ok := item.(IdentifiableItem) if !ok { return ActiveMsg{} @@ -225,7 +310,7 @@ func (s *Selector) activeFilterCmd() tea.Msg { // Here we use VisibleItems because when list.FilterMatchesMsg is sent, // VisibleItems is the only way to get the list of filtered items. The list // bubble should export something like list.FilterMatchesMsg.Items(). - items := s.Model.VisibleItems() + items := s.VisibleItems() if len(items) == 0 { return nil } diff --git a/server/ui/components/statusbar/statusbar.go b/server/ui/components/statusbar/statusbar.go index e5f0f70a5..1dc927c11 100644 --- a/server/ui/components/statusbar/statusbar.go +++ b/server/ui/components/statusbar/statusbar.go @@ -24,12 +24,6 @@ type StatusBar struct { extra string } -// Model is an interface that supports setting the status bar information. -type Model interface { - StatusBarValue() string - StatusBarInfo() string -} - // New creates a new status bar component. func New(c common.Common) *StatusBar { s := &StatusBar{ @@ -53,18 +47,10 @@ func (s *StatusBar) Init() tea.Cmd { func (s *StatusBar) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case StatusBarMsg: - if msg.Key != "" { - s.key = msg.Key - } - if msg.Value != "" { - s.value = msg.Value - } - if msg.Info != "" { - s.info = msg.Info - } - if msg.Extra != "" { - s.extra = msg.Extra - } + s.key = msg.Key + s.value = msg.Value + s.info = msg.Info + s.extra = msg.Extra } return s, nil } diff --git a/server/ui/pages/repo/files.go b/server/ui/pages/repo/files.go index a0a79352b..6968ca897 100644 --- a/server/ui/pages/repo/files.go +++ b/server/ui/pages/repo/files.go @@ -3,11 +3,11 @@ package repo import ( "errors" "fmt" - "log" "path/filepath" "github.com/alecthomas/chroma/lexers" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/proto" @@ -19,7 +19,8 @@ import ( type filesView int const ( - filesViewFiles filesView = iota + filesViewLoading filesView = iota + filesViewFiles filesViewContent ) @@ -58,6 +59,8 @@ type Files struct { currentContent FileContentMsg lastSelected []int lineNumber bool + spinner spinner.Model + cursor int } // NewFiles creates a new files model. @@ -65,7 +68,7 @@ func NewFiles(common common.Common) *Files { f := &Files{ common: common, code: code.New(common, "", ""), - activeView: filesViewFiles, + activeView: filesViewLoading, lastSelected: make([]int, 0), lineNumber: true, } @@ -81,9 +84,17 @@ func NewFiles(common common.Common) *Files { selector.KeyMap.PrevPage = common.KeyMap.PrevPage f.selector = selector f.code.SetShowLineNumber(f.lineNumber) + s := spinner.New(spinner.WithSpinner(spinner.Dot), + spinner.WithStyle(common.Styles.Spinner)) + f.spinner = s return f } +// TabName returns the tab name. +func (f *Files) TabName() string { + return "Files" +} + // SetSize implements common.Component. func (f *Files) SetSize(width, height int) { f.common.SetSize(width, height) @@ -190,10 +201,9 @@ func (f *Files) FullHelp() [][]key.Binding { func (f *Files) Init() tea.Cmd { f.path = "" f.currentItem = nil - f.activeView = filesViewFiles + f.activeView = filesViewLoading f.lastSelected = make([]int, 0) - f.selector.Select(0) - return f.updateFilesCmd + return tea.Batch(f.spinner.Tick, f.updateFilesCmd()) } // Update implements tea.Model. @@ -204,37 +214,42 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.repo = msg case RefMsg: f.ref = msg + f.selector.Select(0) cmds = append(cmds, f.Init()) case FileItemsMsg: cmds = append(cmds, f.selector.SetItems(msg), - updateStatusBarCmd, ) + f.activeView = filesViewFiles + if f.cursor >= 0 { + f.selector.Select(f.cursor) + f.cursor = -1 + } case FileContentMsg: f.activeView = filesViewContent f.currentContent = msg f.code.SetContent(msg.content, msg.ext) f.code.GotoTop() - cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { case FileItem: f.currentItem = &sel f.path = filepath.Join(f.path, sel.entry.Name()) if sel.entry.IsTree() { - cmds = append(cmds, f.selectTreeCmd) + cmds = append(cmds, f.selectTreeCmd()) } else { cmds = append(cmds, f.selectFileCmd) } } case BackMsg: - cmds = append(cmds, f.deselectItemCmd) + f.path = filepath.Dir(f.path) + cmds = append(cmds, f.deselectItemCmd()) case tea.KeyMsg: switch f.activeView { case filesViewFiles: switch { case key.Matches(msg, f.common.KeyMap.SelectItem): - cmds = append(cmds, f.selector.SelectItem) + cmds = append(cmds, f.selector.SelectItemCmd) case key.Matches(msg, f.common.KeyMap.BackItem): cmds = append(cmds, backCmd) } @@ -254,7 +269,7 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch f.activeView { case filesViewFiles: if f.repo != nil { - cmds = append(cmds, f.updateFilesCmd) + cmds = append(cmds, f.updateFilesCmd()) } case filesViewContent: if f.currentContent.content != "" { @@ -265,8 +280,6 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } - case selector.ActiveMsg: - cmds = append(cmds, updateStatusBarCmd) case EmptyRepoMsg: f.ref = nil f.path = "" @@ -275,6 +288,14 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.lastSelected = make([]int, 0) f.selector.Select(0) cmds = append(cmds, f.setItems([]selector.IdentifiableItem{})) + case spinner.TickMsg: + if f.activeView == filesViewLoading && f.spinner.ID() == msg.ID { + s, cmd := f.spinner.Update(msg) + f.spinner = s + if cmd != nil { + cmds = append(cmds, cmd) + } + } } switch f.activeView { case filesViewFiles: @@ -296,6 +317,8 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (f *Files) View() string { switch f.activeView { + case filesViewLoading: + return renderLoading(f.common, f.spinner) case filesViewFiles: return f.selector.View() case filesViewContent: @@ -305,12 +328,16 @@ func (f *Files) View() string { } } +// SpinnerID implements common.TabComponent. +func (f *Files) SpinnerID() int { + return f.spinner.ID() +} + // StatusBarValue returns the status bar value. func (f *Files) StatusBarValue() string { p := f.path if p == "." { - // FIXME: this is a hack to force clear the status bar value - return " " + return "" } return p } @@ -327,7 +354,7 @@ func (f *Files) StatusBarInfo() string { } } -func (f *Files) updateFilesCmd() tea.Msg { +func (f *Files) updateFilesCmd() tea.Cmd { files := make([]selector.IdentifiableItem, 0) dirs := make([]selector.IdentifiableItem, 0) if f.ref == nil { @@ -335,37 +362,38 @@ func (f *Files) updateFilesCmd() tea.Msg { } r, err := f.repo.Open() if err != nil { - return common.ErrorMsg(err) + return common.ErrorCmd(err) } - t, err := r.TreePath(f.ref, f.path) - if err != nil { - log.Printf("ui: files: error getting tree %v", err) - return common.ErrorMsg(err) - } - ents, err := t.Entries() - if err != nil { - log.Printf("ui: files: error listing files %v", err) - return common.ErrorMsg(err) - } - ents.Sort() - for _, e := range ents { - if e.IsTree() { - dirs = append(dirs, FileItem{entry: e}) - } else { - files = append(files, FileItem{entry: e}) + path := f.path + ref := f.ref + return func() tea.Msg { + t, err := r.TreePath(ref, path) + if err != nil { + return common.ErrorCmd(err) } + ents, err := t.Entries() + if err != nil { + return common.ErrorCmd(err) + } + ents.Sort() + for _, e := range ents { + if e.IsTree() { + dirs = append(dirs, FileItem{entry: e}) + } else { + files = append(files, FileItem{entry: e}) + } + } + return FileItemsMsg(append(dirs, files...)) } - return FileItemsMsg(append(dirs, files...)) } -func (f *Files) selectTreeCmd() tea.Msg { +func (f *Files) selectTreeCmd() tea.Cmd { if f.currentItem != nil && f.currentItem.entry.IsTree() { f.lastSelected = append(f.lastSelected, f.selector.Index()) - f.selector.Select(0) + f.cursor = 0 return f.updateFilesCmd() } - log.Printf("ui: files: current item is not a tree") - return common.ErrorMsg(errNoFileSelected) + return common.ErrorCmd(errNoFileSelected) } func (f *Files) selectFileCmd() tea.Msg { @@ -373,7 +401,6 @@ func (f *Files) selectFileCmd() tea.Msg { if i != nil && !i.entry.IsTree() { fi := i.entry.File() if i.Mode().IsDir() || f == nil { - log.Printf("ui: files: current item is not a file") return common.ErrorMsg(errInvalidFile) } @@ -391,32 +418,25 @@ func (f *Files) selectFileCmd() tea.Msg { break } } - } else { - log.Printf("ui: files: error checking attributes %v", err) } - } else { - log.Printf("ui: files: error opening repo %v", err) } if !bin { bin, err = fi.IsBinary() if err != nil { f.path = filepath.Dir(f.path) - log.Printf("ui: files: error checking if file is binary %v", err) return common.ErrorMsg(err) } } if bin { f.path = filepath.Dir(f.path) - log.Printf("ui: files: file is binary") return common.ErrorMsg(errBinaryFile) } c, err := fi.Bytes() if err != nil { f.path = filepath.Dir(f.path) - log.Printf("ui: files: error reading file %v", err) return common.ErrorMsg(err) } @@ -424,21 +444,18 @@ func (f *Files) selectFileCmd() tea.Msg { return FileContentMsg{string(c), i.entry.Name()} } - log.Printf("ui: files: current item is not a file") return common.ErrorMsg(errNoFileSelected) } -func (f *Files) deselectItemCmd() tea.Msg { - f.path = filepath.Dir(f.path) - f.activeView = filesViewFiles - msg := f.updateFilesCmd() +func (f *Files) deselectItemCmd() tea.Cmd { index := 0 if len(f.lastSelected) > 0 { index = f.lastSelected[len(f.lastSelected)-1] f.lastSelected = f.lastSelected[:len(f.lastSelected)-1] } - f.selector.Select(index) - return msg + f.cursor = index + f.activeView = filesViewFiles + return f.updateFilesCmd() } func (f *Files) setItems(items []selector.IdentifiableItem) tea.Cmd { diff --git a/server/ui/pages/repo/log.go b/server/ui/pages/repo/log.go index 60659e5a6..e8666e4bf 100644 --- a/server/ui/pages/repo/log.go +++ b/server/ui/pages/repo/log.go @@ -25,7 +25,8 @@ var waitBeforeLoading = time.Millisecond * 100 type logView int const ( - logViewCommits logView = iota + logViewLoading logView = iota + logViewCommits logViewDiff ) @@ -55,7 +56,6 @@ type Log struct { selectedCommit *git.Commit currentDiff *git.Diff loadingTime time.Time - loading bool spinner spinner.Model } @@ -83,6 +83,11 @@ func NewLog(common common.Common) *Log { return l } +// TabName returns the name of the tab. +func (l *Log) TabName() string { + return "Commits" +} + // SetSize implements common.Component. func (l *Log) SetSize(width, height int) { l.common.SetSize(width, height) @@ -163,15 +168,10 @@ func (l *Log) FullHelp() [][]key.Binding { func (l *Log) startLoading() tea.Cmd { l.loadingTime = time.Now() - l.loading = true + l.activeView = logViewLoading return l.spinner.Tick } -func (l *Log) stopLoading() tea.Cmd { - l.loading = false - return updateStatusBarCmd -} - // Init implements tea.Model. func (l *Log) Init() tea.Cmd { l.activeView = logViewCommits @@ -179,9 +179,8 @@ func (l *Log) Init() tea.Cmd { l.count = 0 l.activeCommit = nil l.selectedCommit = nil - l.selector.Select(0) return tea.Batch( - l.updateCommitsCmd, + l.countCommitsCmd, // start loading on init l.startLoading(), ) @@ -195,15 +194,17 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { l.repo = msg case RefMsg: l.ref = msg + l.selector.Select(0) cmds = append(cmds, l.Init()) case LogCountMsg: l.count = int64(msg) + l.selector.SetTotalPages(int(msg)) + l.selector.SetItems(make([]selector.IdentifiableItem, l.count)) + cmds = append(cmds, l.updateCommitsCmd) case LogItemsMsg: - cmds = append(cmds, - l.selector.SetItems(msg), - // stop loading after receiving items - l.stopLoading(), - ) + // stop loading after receiving items + l.activeView = logViewCommits + cmds = append(cmds, l.selector.SetItems(msg)) l.selector.SetPage(l.nextPage) l.SetSize(l.common.Width, l.common.Height) i := l.selector.SelectedItem() @@ -217,10 +218,11 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(kmsg, l.common.KeyMap.SelectItem): - cmds = append(cmds, l.selector.SelectItem) + cmds = append(cmds, l.selector.SelectItemCmd) } } - // This is a hack for loading commits on demand based on list.Pagination. + // XXX: This is a hack for loading commits on demand based on + // list.Pagination. curPage := l.selector.Page() s, cmd := l.selector.Update(msg) m := s.(*selector.Selector) @@ -247,14 +249,12 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if l.activeView == logViewDiff { l.activeView = logViewCommits l.selectedCommit = nil - cmds = append(cmds, updateStatusBarCmd) } case selector.ActiveMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: l.activeCommit = sel.Commit } - cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: @@ -277,11 +277,6 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ) l.vp.GotoTop() l.activeView = logViewDiff - cmds = append(cmds, - updateStatusBarCmd, - // stop loading after setting the viewport content - l.stopLoading(), - ) case footer.ToggleFooterMsg: cmds = append(cmds, l.updateCommitsCmd) case tea.WindowSizeMsg: @@ -304,7 +299,6 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case EmptyRepoMsg: l.ref = nil - l.loading = false l.activeView = logViewCommits l.nextPage = 0 l.count = 0 @@ -312,13 +306,14 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { l.selectedCommit = nil l.selector.Select(0) cmds = append(cmds, l.setItems([]selector.IdentifiableItem{})) - } - if l.loading { - s, cmd := l.spinner.Update(msg) - if cmd != nil { - cmds = append(cmds, cmd) + case spinner.TickMsg: + if l.activeView == logViewLoading && l.spinner.ID() == msg.ID { + s, cmd := l.spinner.Update(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + l.spinner = s } - l.spinner = s } switch l.activeView { case logViewDiff: @@ -333,17 +328,19 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (l *Log) View() string { - if l.loading && l.loadingTime.Add(waitBeforeLoading).Before(time.Now()) { - msg := fmt.Sprintf("%s loading commit", l.spinner.View()) - if l.selectedCommit == nil { - msg += "s" - } - msg += "…" - return l.common.Styles.SpinnerContainer.Copy(). - Height(l.common.Height). - Render(msg) - } switch l.activeView { + case logViewLoading: + if l.loadingTime.Add(waitBeforeLoading).Before(time.Now()) { + msg := fmt.Sprintf("%s loading commit", l.spinner.View()) + if l.selectedCommit == nil { + msg += "s" + } + msg += "…" + return l.common.Styles.SpinnerContainer.Copy(). + Height(l.common.Height). + Render(msg) + } + fallthrough case logViewCommits: return l.selector.View() case logViewDiff: @@ -353,9 +350,14 @@ func (l *Log) View() string { } } +// SpinnerID implements common.TabComponent. +func (l *Log) SpinnerID() int { + return l.spinner.ID() +} + // StatusBarValue returns the status bar value. func (l *Log) StatusBarValue() string { - if l.loading { + if l.activeView == logViewLoading { return "" } c := l.activeCommit @@ -376,6 +378,11 @@ func (l *Log) StatusBarValue() string { // StatusBarInfo returns the status bar info. func (l *Log) StatusBarInfo() string { switch l.activeView { + case logViewLoading: + if l.count == 0 { + return "" + } + fallthrough case logViewCommits: // We're using l.nextPage instead of l.selector.Paginator.Page because // of the paginator hack above. @@ -404,28 +411,26 @@ func (l *Log) countCommitsCmd() tea.Msg { } func (l *Log) updateCommitsCmd() tea.Msg { - count := l.count - if l.count == 0 { - switch msg := l.countCommitsCmd().(type) { - case common.ErrorMsg: - return msg - case LogCountMsg: - count = int64(msg) - } - } if l.ref == nil { return nil } - items := make([]selector.IdentifiableItem, count) - page := l.nextPage - limit := l.selector.PerPage() - skip := page * limit r, err := l.repo.Open() if err != nil { return common.ErrorMsg(err) } + + count := l.count + if count == 0 { + return LogItemsMsg([]selector.IdentifiableItem{}) + } + + page := l.nextPage + limit := l.selector.PerPage() + skip := page * limit + ref := l.ref + items := make([]selector.IdentifiableItem, count) // CommitsByPage pages start at 1 - cc, err := r.CommitsByPage(l.ref, page+1, limit) + cc, err := r.CommitsByPage(ref, page+1, limit) if err != nil { l.common.Logger.Debugf("ui: error loading commits: %v", err) return common.ErrorMsg(err) @@ -447,6 +452,9 @@ func (l *Log) selectCommitCmd(commit *git.Commit) tea.Cmd { } func (l *Log) loadDiffCmd() tea.Msg { + if l.selectedCommit == nil { + return nil + } r, err := l.repo.Open() if err != nil { l.common.Logger.Debugf("ui: error loading diff repository: %v", err) diff --git a/server/ui/pages/repo/readme.go b/server/ui/pages/repo/readme.go index 6c6b610f7..256e5ab50 100644 --- a/server/ui/pages/repo/readme.go +++ b/server/ui/pages/repo/readme.go @@ -5,6 +5,7 @@ import ( "path/filepath" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/soft-serve/server/backend" "github.com/charmbracelet/soft-serve/server/proto" @@ -14,7 +15,8 @@ import ( // ReadmeMsg is a message sent when the readme is loaded. type ReadmeMsg struct { - Msg tea.Msg + Content string + Path string } // Readme is the readme component page. @@ -24,18 +26,29 @@ type Readme struct { ref RefMsg repo proto.Repository readmePath string + spinner spinner.Model + isLoading bool } // NewReadme creates a new readme model. func NewReadme(common common.Common) *Readme { readme := code.New(common, "", "") readme.NoContentStyle = readme.NoContentStyle.Copy().SetString("No readme found.") + s := spinner.New(spinner.WithSpinner(spinner.Dot), + spinner.WithStyle(common.Styles.Spinner)) return &Readme{ - code: readme, - common: common, + code: readme, + common: common, + spinner: s, + isLoading: true, } } +// TabName returns the name of the tab. +func (r *Readme) TabName() string { + return "Readme" +} + // SetSize implements common.Component. func (r *Readme) SetSize(width, height int) { r.common.SetSize(width, height) @@ -72,7 +85,8 @@ func (r *Readme) FullHelp() [][]key.Binding { // Init implements tea.Model. func (r *Readme) Init() tea.Cmd { - return r.updateReadmeCmd + r.isLoading = true + return tea.Batch(r.spinner.Tick, r.updateReadmeCmd) } // Update implements tea.Model. @@ -87,6 +101,19 @@ func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case EmptyRepoMsg: r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(), r.repo.Name()), ".md") + case ReadmeMsg: + r.isLoading = false + r.readmePath = msg.Path + r.code.GotoTop() + cmds = append(cmds, r.code.SetContent(msg.Content, msg.Path)) + case spinner.TickMsg: + if r.isLoading && r.spinner.ID() == msg.ID { + s, cmd := r.spinner.Update(msg) + r.spinner = s + if cmd != nil { + cmds = append(cmds, cmd) + } + } } c, cmd := r.code.Update(msg) r.code = c.(*code.Code) @@ -98,9 +125,17 @@ func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (r *Readme) View() string { + if r.isLoading { + return renderLoading(r.common, r.spinner) + } return r.code.View() } +// SpinnerID implements common.TabComponent. +func (r *Readme) SpinnerID() int { + return r.spinner.ID() +} + // StatusBarValue implements statusbar.StatusBar. func (r *Readme) StatusBarValue() string { dir := filepath.Dir(r.readmePath) @@ -118,14 +153,11 @@ func (r *Readme) StatusBarInfo() string { func (r *Readme) updateReadmeCmd() tea.Msg { m := ReadmeMsg{} if r.repo == nil { - return common.ErrorCmd(common.ErrMissingRepo) + return common.ErrorMsg(common.ErrMissingRepo) } + // TODO: display the readme of the current branch/tag rm, rp, _ := backend.Readme(r.repo) - r.readmePath = rp - r.code.GotoTop() - cmd := r.code.SetContent(rm, rp) - if cmd != nil { - m.Msg = cmd() - } + m.Content = rm + m.Path = rp return m } diff --git a/server/ui/pages/repo/refs.go b/server/ui/pages/repo/refs.go index 2dac5b381..d83bd336c 100644 --- a/server/ui/pages/repo/refs.go +++ b/server/ui/pages/repo/refs.go @@ -6,13 +6,13 @@ import ( "strings" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/soft-serve/git" ggit "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/proto" "github.com/charmbracelet/soft-serve/server/ui/common" "github.com/charmbracelet/soft-serve/server/ui/components/selector" - "github.com/charmbracelet/soft-serve/server/ui/components/tabs" ) // RefMsg is a message that contains a git.Reference. @@ -32,6 +32,8 @@ type Refs struct { ref *git.Reference activeRef *git.Reference refPrefix string + spinner spinner.Model + isLoading bool } // NewRefs creates a new Refs component. @@ -39,6 +41,7 @@ func NewRefs(common common.Common, refPrefix string) *Refs { r := &Refs{ common: common, refPrefix: refPrefix, + isLoading: true, } s := selector.New(common, []selector.IdentifiableItem{}, RefItemDelegate{&common}) s.SetShowFilter(false) @@ -49,9 +52,23 @@ func NewRefs(common common.Common, refPrefix string) *Refs { s.SetFilteringEnabled(false) s.DisableQuitKeybindings() r.selector = s + sp := spinner.New(spinner.WithSpinner(spinner.Dot), + spinner.WithStyle(common.Styles.Spinner)) + r.spinner = sp return r } +// TabName returns the name of the tab. +func (r *Refs) TabName() string { + if r.refPrefix == git.RefsHeads { + return "Branches" + } else if r.refPrefix == git.RefsTags { + return "Tags" + } else { + return "Refs" + } +} + // SetSize implements common.Component. func (r *Refs) SetSize(width, height int) { r.common.SetSize(width, height) @@ -94,7 +111,8 @@ func (r *Refs) FullHelp() [][]key.Binding { // Init implements tea.Model. func (r *Refs) Init() tea.Cmd { - return r.updateItemsCmd + r.isLoading = true + return tea.Batch(r.spinner.Tick, r.updateItemsCmd) } // Update implements tea.Model. @@ -114,29 +132,37 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if i != nil { r.activeRef = i.(RefItem).Reference } + r.isLoading = false } case selector.ActiveMsg: switch sel := msg.IdentifiableItem.(type) { case RefItem: r.activeRef = sel.Reference } - cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch i := msg.IdentifiableItem.(type) { case RefItem: cmds = append(cmds, switchRefCmd(i.Reference), - tabs.SelectTabCmd(int(filesTab)), + switchTabCmd(&Files{}), ) } case tea.KeyMsg: switch { case key.Matches(msg, r.common.KeyMap.SelectItem): - cmds = append(cmds, r.selector.SelectItem) + cmds = append(cmds, r.selector.SelectItemCmd) } case EmptyRepoMsg: r.ref = nil cmds = append(cmds, r.setItems([]selector.IdentifiableItem{})) + case spinner.TickMsg: + if r.isLoading && r.spinner.ID() == msg.ID { + s, cmd := r.spinner.Update(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + r.spinner = s + } } m, cmd := r.selector.Update(msg) r.selector = m.(*selector.Selector) @@ -148,9 +174,17 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (r *Refs) View() string { + if r.isLoading { + return renderLoading(r.common, r.spinner) + } return r.selector.View() } +// SpinnerID implements common.TabComponent. +func (r *Refs) SpinnerID() int { + return r.spinner.ID() +} + // StatusBarValue implements statusbar.StatusBar. func (r *Refs) StatusBarValue() string { if r.activeRef == nil { diff --git a/server/ui/pages/repo/repo.go b/server/ui/pages/repo/repo.go index 3f1927ba8..06bdaa738 100644 --- a/server/ui/pages/repo/repo.go +++ b/server/ui/pages/repo/repo.go @@ -23,27 +23,6 @@ const ( readyState ) -type tab int - -const ( - readmeTab tab = iota - filesTab - commitsTab - branchesTab - tagsTab - lastTab -) - -func (t tab) String() string { - return []string{ - "Readme", - "Files", - "Commits", - "Branches", - "Tags", - }[t] -} - // EmptyRepoMsg is a message to indicate that the repository is empty. type EmptyRepoMsg struct{} @@ -69,48 +48,36 @@ type CopyMsg struct { type Repo struct { common common.Common selectedRepo proto.Repository - activeTab tab + activeTab int tabs *tabs.Tabs statusbar *statusbar.StatusBar - panes []common.Component + panes []common.TabComponent ref *git.Reference state state spinner spinner.Model - panesReady [lastTab]bool + panesReady []bool } // New returns a new Repo. -func New(c common.Common) *Repo { +func New(c common.Common, comps ...common.TabComponent) *Repo { sb := statusbar.New(c) - ts := make([]string, lastTab) - // Tabs must match the order of tab constants above. - for i, t := range []tab{readmeTab, filesTab, commitsTab, branchesTab, tagsTab} { - ts[i] = t.String() + ts := make([]string, 0) + for _, c := range comps { + ts = append(ts, c.TabName()) } c.Logger = c.Logger.WithPrefix("ui.repo") tb := tabs.New(c, ts) - readme := NewReadme(c) - log := NewLog(c) - files := NewFiles(c) - branches := NewRefs(c, git.RefsHeads) - tags := NewRefs(c, git.RefsTags) // Make sure the order matches the order of tab constants above. - panes := []common.Component{ - readme, - files, - log, - branches, - tags, - } s := spinner.New(spinner.WithSpinner(spinner.Dot), spinner.WithStyle(c.Styles.Spinner)) r := &Repo{ - common: c, - tabs: tb, - statusbar: sb, - panes: panes, - state: loadingState, - spinner: s, + common: c, + tabs: tb, + statusbar: sb, + panes: comps, + state: loadingState, + spinner: s, + panesReady: make([]bool, len(comps)), } return r } @@ -157,9 +124,13 @@ func (r *Repo) FullHelp() [][]key.Binding { // Init implements tea.View. func (r *Repo) Init() tea.Cmd { + r.state = loadingState + // r.panesReady = make([]bool, len(r.panes)) + r.activeTab = 0 return tea.Batch( r.tabs.Init(), r.statusbar.Init(), + r.spinner.Tick, ) } @@ -169,40 +140,27 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case RepoMsg: // Set the state to loading when we get a new repository. - r.state = loadingState - r.panesReady = [lastTab]bool{} - r.activeTab = 0 r.selectedRepo = msg cmds = append(cmds, - r.tabs.Init(), + r.Init(), // This will set the selected repo in each pane's model. r.updateModels(msg), - r.spinner.Tick, ) case RefMsg: r.ref = msg - for _, p := range r.panes { - // Init will initiate each pane's model with its contents. - cmds = append(cmds, p.Init()) - } cmds = append(cmds, - r.updateStatusBarCmd, r.updateModels(msg), ) + r.state = readyState case tabs.SelectTabMsg: - r.activeTab = tab(msg) + r.activeTab = int(msg) t, cmd := r.tabs.Update(msg) r.tabs = t.(*tabs.Tabs) if cmd != nil { cmds = append(cmds, cmd) } case tabs.ActiveTabMsg: - r.activeTab = tab(msg) - if r.selectedRepo != nil { - cmds = append(cmds, - r.updateStatusBarCmd, - ) - } + r.activeTab = int(msg) case tea.KeyMsg, tea.MouseMsg: t, cmd := r.tabs.Update(msg) r.tabs = t.(*tabs.Tabs) @@ -210,7 +168,6 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } if r.selectedRepo != nil { - cmds = append(cmds, r.updateStatusBarCmd) urlID := fmt.Sprintf("%s-url", r.selectedRepo.Name()) cmd := common.CloneCmd(r.common.Config().SSH.PublicURL, r.selectedRepo.Name()) if msg, ok := msg.(tea.MouseMsg); ok && r.common.Zone.Get(urlID).InBounds(msg) { @@ -242,48 +199,68 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { Value: msg.Message, } }) - case ReadmeMsg, FileItemsMsg, LogCountMsg, LogItemsMsg, RefItemsMsg: - cmds = append(cmds, r.updateRepo(msg)) + case ReadmeMsg: + cmds = append(cmds, r.updateTabComponent(&Readme{}, msg)) + case FileItemsMsg: + cmds = append(cmds, r.updateTabComponent(&Files{}, msg)) + case LogCountMsg, LogItemsMsg: + cmds = append(cmds, r.updateTabComponent(&Log{}, msg)) + case RefItemsMsg: + cmds = append(cmds, r.updateTabComponent(&Refs{refPrefix: msg.prefix}, msg)) // We have two spinners, one is used to when loading the repository and the // other is used when loading the log. // Check if the spinner ID matches the spinner model. case spinner.TickMsg: - switch msg.ID { - case r.spinner.ID(): - if r.state == loadingState { - s, cmd := r.spinner.Update(msg) - r.spinner = s - if cmd != nil { - cmds = append(cmds, cmd) + if r.state == loadingState && r.spinner.ID() == msg.ID { + s, cmd := r.spinner.Update(msg) + r.spinner = s + if cmd != nil { + cmds = append(cmds, cmd) + } + } else { + for i, c := range r.panes { + if c.SpinnerID() == msg.ID { + m, cmd := c.Update(msg) + r.panes[i] = m.(common.TabComponent) + if cmd != nil { + cmds = append(cmds, cmd) + } + break } } - default: - cmds = append(cmds, r.updateRepo(msg)) } - case UpdateStatusBarMsg: - cmds = append(cmds, r.updateStatusBarCmd) case tea.WindowSizeMsg: + r.SetSize(msg.Width, msg.Height) cmds = append(cmds, r.updateModels(msg)) case EmptyRepoMsg: r.ref = nil r.state = readyState - cmds = append(cmds, - r.updateModels(msg), - r.updateStatusBarCmd, - ) + cmds = append(cmds, r.updateModels(msg)) case common.ErrorMsg: r.state = readyState + case SwitchTabMsg: + for i, c := range r.panes { + if c.TabName() == msg.TabName() { + cmds = append(cmds, tabs.SelectTabCmd(i)) + break + } + } } - s, cmd := r.statusbar.Update(msg) - r.statusbar = s.(*statusbar.StatusBar) + active := r.panes[r.activeTab] + m, cmd := active.Update(msg) + r.panes[r.activeTab] = m.(common.TabComponent) if cmd != nil { cmds = append(cmds, cmd) } - m, cmd := r.panes[r.activeTab].Update(msg) - r.panes[r.activeTab] = m.(common.Component) - if cmd != nil { - cmds = append(cmds, cmd) + + if r.selectedRepo != nil { + s, cmd := r.statusbar.Update(r.makeStatusBarMsg()) + r.statusbar = s.(*statusbar.StatusBar) + if cmd != nil { + cmds = append(cmds, cmd) + } } + return r, tea.Batch(cmds...) } @@ -364,12 +341,10 @@ func (r *Repo) headerView() string { ) } -func (r *Repo) updateStatusBarCmd() tea.Msg { - if r.selectedRepo == nil { - return nil - } - value := r.panes[r.activeTab].(statusbar.Model).StatusBarValue() - info := r.panes[r.activeTab].(statusbar.Model).StatusBarInfo() +func (r *Repo) makeStatusBarMsg() statusbar.StatusBarMsg { + active := r.panes[r.activeTab] + value := active.StatusBarValue() + info := active.StatusBarInfo() branch := "*" if r.ref != nil { branch += " " + r.ref.Name().Short() @@ -382,79 +357,33 @@ func (r *Repo) updateStatusBarCmd() tea.Msg { } } -func (r *Repo) updateModels(msg tea.Msg) tea.Cmd { +func (r *Repo) updateTabComponent(c common.TabComponent, msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, 0) for i, b := range r.panes { - m, cmd := b.Update(msg) - r.panes[i] = m.(common.Component) - if cmd != nil { - cmds = append(cmds, cmd) + if b.TabName() == c.TabName() { + m, cmd := b.Update(msg) + r.panes[i] = m.(common.TabComponent) + if cmd != nil { + cmds = append(cmds, cmd) + } + break } } return tea.Batch(cmds...) } -func (r *Repo) updateRepo(msg tea.Msg) tea.Cmd { +func (r *Repo) updateModels(msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, 0) - switch msg := msg.(type) { - case LogCountMsg, LogItemsMsg, spinner.TickMsg: - switch msg.(type) { - case LogItemsMsg: - r.panesReady[commitsTab] = true - } - l, cmd := r.panes[commitsTab].Update(msg) - r.panes[commitsTab] = l.(*Log) - if cmd != nil { - cmds = append(cmds, cmd) - } - case FileItemsMsg: - r.panesReady[filesTab] = true - f, cmd := r.panes[filesTab].Update(msg) - r.panes[filesTab] = f.(*Files) + for i, b := range r.panes { + m, cmd := b.Update(msg) + r.panes[i] = m.(common.TabComponent) if cmd != nil { cmds = append(cmds, cmd) } - case RefItemsMsg: - switch msg.prefix { - case git.RefsHeads: - r.panesReady[branchesTab] = true - b, cmd := r.panes[branchesTab].Update(msg) - r.panes[branchesTab] = b.(*Refs) - if cmd != nil { - cmds = append(cmds, cmd) - } - case git.RefsTags: - r.panesReady[tagsTab] = true - t, cmd := r.panes[tagsTab].Update(msg) - r.panes[tagsTab] = t.(*Refs) - if cmd != nil { - cmds = append(cmds, cmd) - } - } - case ReadmeMsg: - r.panesReady[readmeTab] = true - } - if r.isReady() { - r.state = readyState } return tea.Batch(cmds...) } -func (r *Repo) isReady() bool { - ready := true - // We purposely ignore the log pane here because it has its own spinner. - for _, b := range []bool{ - r.panesReady[filesTab], r.panesReady[branchesTab], - r.panesReady[tagsTab], r.panesReady[readmeTab], - } { - if !b { - ready = false - break - } - } - return ready -} - func copyCmd(text, msg string) tea.Cmd { return func() tea.Msg { return CopyMsg{ @@ -464,10 +393,21 @@ func copyCmd(text, msg string) tea.Cmd { } } -func updateStatusBarCmd() tea.Msg { - return UpdateStatusBarMsg{} -} - func backCmd() tea.Msg { return BackMsg{} } + +type SwitchTabMsg common.TabComponent + +func switchTabCmd(m common.TabComponent) tea.Cmd { + return func() tea.Msg { + return SwitchTabMsg(m) + } +} + +func renderLoading(c common.Common, s spinner.Model) string { + msg := fmt.Sprintf("%s loading…", s.View()) + return c.Styles.SpinnerContainer.Copy(). + Height(c.Height). + Render(msg) +} From 7ba5a5781c32f33d781a057f5903d0cbc4328206 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 5 Oct 2023 15:16:22 -0400 Subject: [PATCH 07/12] feat(ui): update readme based on selected reference --- cmd/soft/migrate_config.go | 2 +- git/utils.go | 13 ++++++++----- server/backend/utils.go | 8 ++++---- server/ui/pages/repo/readme.go | 3 +-- server/ui/pages/selection/selection.go | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cmd/soft/migrate_config.go b/cmd/soft/migrate_config.go index 86bea23d3..5138e93c0 100644 --- a/cmd/soft/migrate_config.go +++ b/cmd/soft/migrate_config.go @@ -124,7 +124,7 @@ var migrateConfig = &cobra.Command{ } } - readme, readmePath, err := git.LatestFile(r, "README*") + readme, readmePath, err := git.LatestFile(r, nil, "README*") hasReadme := err == nil // Set server name diff --git a/git/utils.go b/git/utils.go index 3710e172d..b4ca50fc6 100644 --- a/git/utils.go +++ b/git/utils.go @@ -8,14 +8,17 @@ import ( ) // LatestFile returns the contents of the first file at the specified path pattern in the repository and its file path. -func LatestFile(repo *Repository, pattern string) (string, string, error) { +func LatestFile(repo *Repository, ref *Reference, pattern string) (string, string, error) { g := glob.MustCompile(pattern) dir := filepath.Dir(pattern) - head, err := repo.HEAD() - if err != nil { - return "", "", err + if ref == nil { + head, err := repo.HEAD() + if err != nil { + return "", "", err + } + ref = head } - t, err := repo.TreePath(head, dir) + t, err := repo.TreePath(ref, dir) if err != nil { return "", "", err } diff --git a/server/backend/utils.go b/server/backend/utils.go index 024ba8af2..5e90873e5 100644 --- a/server/backend/utils.go +++ b/server/backend/utils.go @@ -7,17 +7,17 @@ import ( // LatestFile returns the contents of the latest file at the specified path in // the repository and its file path. -func LatestFile(r proto.Repository, pattern string) (string, string, error) { +func LatestFile(r proto.Repository, ref *git.Reference, pattern string) (string, string, error) { repo, err := r.Open() if err != nil { return "", "", err } - return git.LatestFile(repo, pattern) + return git.LatestFile(repo, ref, pattern) } // Readme returns the repository's README. -func Readme(r proto.Repository) (readme string, path string, err error) { +func Readme(r proto.Repository, ref *git.Reference) (readme string, path string, err error) { pattern := "[rR][eE][aA][dD][mM][eE]*" - readme, path, err = LatestFile(r, pattern) + readme, path, err = LatestFile(r, ref, pattern) return } diff --git a/server/ui/pages/repo/readme.go b/server/ui/pages/repo/readme.go index 256e5ab50..b76dcf82c 100644 --- a/server/ui/pages/repo/readme.go +++ b/server/ui/pages/repo/readme.go @@ -155,8 +155,7 @@ func (r *Readme) updateReadmeCmd() tea.Msg { if r.repo == nil { return common.ErrorMsg(common.ErrMissingRepo) } - // TODO: display the readme of the current branch/tag - rm, rp, _ := backend.Readme(r.repo) + rm, rp, _ := backend.Readme(r.repo, r.ref) m.Content = rm m.Path = rp return m diff --git a/server/ui/pages/selection/selection.go b/server/ui/pages/selection/selection.go index b85573d33..b84b0a33d 100644 --- a/server/ui/pages/selection/selection.go +++ b/server/ui/pages/selection/selection.go @@ -201,7 +201,7 @@ func (s *Selection) Init() tea.Cmd { sortedItems := make(Items, 0) for _, r := range repos { if r.Name() == ".soft-serve" { - readme, path, err := backend.Readme(r) + readme, path, err := backend.Readme(r, nil) if err != nil { continue } From fd024099500440e8564bd926130fc341a13f9802 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 5 Oct 2023 17:22:58 -0400 Subject: [PATCH 08/12] feat(ui): add branch/tag commit date and hash Fixes: https://github.com/charmbracelet/soft-serve/issues/382 --- git/commit.go | 22 +------ git/patch.go | 8 +-- git/reference.go | 28 +-------- git/repo.go | 11 +--- git/tag.go | 5 ++ server/ssh/cmd/blob.go | 2 +- server/ssh/cmd/commit.go | 7 +-- server/ssh/cmd/tree.go | 2 +- server/ui/pages/repo/refs.go | 19 ++++-- server/ui/pages/repo/refsitem.go | 102 ++++++++++++++++++++++++------- server/ui/styles/styles.go | 34 +++++++++-- 11 files changed, 143 insertions(+), 97 deletions(-) create mode 100644 git/tag.go diff --git a/git/commit.go b/git/commit.go index 3dd0a8ff3..1e955ff31 100644 --- a/git/commit.go +++ b/git/commit.go @@ -4,27 +4,11 @@ import ( "github.com/gogs/git-module" ) -// ZeroHash is the zero hash. -var ZeroHash Hash = git.EmptyID - -// Hash represents a git hash. -type Hash string - -// String returns the string representation of a hash as a string. -func (h Hash) String() string { - return string(h) -} - -// SHA1 represents the hash as a SHA1. -func (h Hash) SHA1() *git.SHA1 { - return git.MustIDFromString(h.String()) -} +// ZeroID is the zero hash. +const ZeroID = git.EmptyID // Commit is a wrapper around git.Commit with helper methods. -type Commit struct { - *git.Commit - Hash Hash -} +type Commit = git.Commit // Commits is a list of commits. type Commits []*Commit diff --git a/git/patch.go b/git/patch.go index aaa6b9d78..f20312cc4 100644 --- a/git/patch.go +++ b/git/patch.go @@ -115,14 +115,14 @@ func (f *DiffFileChange) Mode() git.EntryMode { // Files returns the diff files. func (f *DiffFile) Files() (from *DiffFileChange, to *DiffFileChange) { - if f.OldIndex != ZeroHash.String() { + if f.OldIndex != ZeroID { from = &DiffFileChange{ hash: f.OldIndex, name: f.OldName(), mode: f.OldMode(), } } - if f.Index != ZeroHash.String() { + if f.Index != ZeroID { to = &DiffFileChange{ hash: f.Index, name: f.Name, @@ -298,14 +298,14 @@ func writeFilePatchHeader(sb *strings.Builder, filePatch *DiffFile) { lines = append(lines, fmt.Sprintf("diff --git %s %s", srcPrefix+to.Name(), dstPrefix+to.Name()), fmt.Sprintf("new file mode %o", to.Mode()), - fmt.Sprintf("index %s..%s", ZeroHash, to.Hash()), + fmt.Sprintf("index %s..%s", ZeroID, to.Hash()), ) lines = appendPathLines(lines, "/dev/null", dstPrefix+to.Name(), isBinary) case to == nil: lines = append(lines, fmt.Sprintf("diff --git %s %s", srcPrefix+from.Name(), dstPrefix+from.Name()), fmt.Sprintf("deleted file mode %o", from.Mode()), - fmt.Sprintf("index %s..%s", from.Hash(), ZeroHash), + fmt.Sprintf("index %s..%s", from.Hash(), ZeroID), ) lines = appendPathLines(lines, srcPrefix+from.Name(), "/dev/null", isBinary) } diff --git a/git/reference.go b/git/reference.go index ec01a53fd..47aceb95b 100644 --- a/git/reference.go +++ b/git/reference.go @@ -18,23 +18,12 @@ const ( // Reference is a wrapper around git.Reference with helper methods. type Reference struct { *git.Reference - Hash Hash path string // repo path } // ReferenceName is a Refspec wrapper. type ReferenceName string -// NewReference creates a new reference. -func NewReference(rp, refspec string) *Reference { - return &Reference{ - Reference: &git.Reference{ - Refspec: refspec, - }, - path: rp, - } -} - // String returns the reference name i.e. refs/heads/master. func (r ReferenceName) String() string { return string(r) @@ -42,11 +31,7 @@ func (r ReferenceName) String() string { // Short returns the short name of the reference i.e. master. func (r ReferenceName) Short() string { - s := strings.Split(r.String(), "/") - if len(s) > 0 { - return s[len(s)-1] - } - return r.String() + return git.RefShortName(string(r)) } // Name returns the reference name i.e. refs/heads/master. @@ -63,14 +48,3 @@ func (r *Reference) IsBranch() bool { func (r *Reference) IsTag() bool { return strings.HasPrefix(r.Refspec, git.RefsTags) } - -// TargetHash returns the hash of the reference target. -func (r *Reference) TargetHash() Hash { - if r.IsTag() { - id, err := git.ShowRefVerify(r.path, r.Refspec) - if err == nil { - return Hash(id) - } - } - return r.Hash -} diff --git a/git/repo.go b/git/repo.go index ef3d2759d..e25b1c0dc 100644 --- a/git/repo.go +++ b/git/repo.go @@ -77,7 +77,6 @@ func (r *Repository) HEAD() (*Reference, error) { ID: hash, Refspec: rn, }, - Hash: Hash(hash), path: r.Path, }, nil } @@ -92,7 +91,6 @@ func (r *Repository) References() ([]*Reference, error) { for _, ref := range refs { rrefs = append(rrefs, &Reference{ Reference: ref, - Hash: Hash(ref.ID), path: r.Path, }) } @@ -121,7 +119,7 @@ func (r *Repository) Tree(ref *Reference) (*Tree, error) { } ref = rref } - return r.LsTree(ref.Hash.String()) + return r.LsTree(ref.ID) } // TreePath returns the tree for the given path. @@ -142,7 +140,7 @@ func (r *Repository) TreePath(ref *Reference, path string) (*Tree, error) { // Diff returns the diff for the given commit. func (r *Repository) Diff(commit *Commit) (*Diff, error) { - ddiff, err := r.Repository.Diff(commit.Hash.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars, git.DiffOptions{ + ddiff, err := r.Repository.Diff(commit.ID.String(), DiffMaxFiles, DiffMaxFileLines, DiffMaxLineChars, git.DiffOptions{ CommandOptions: git.CommandOptions{ Envs: []string{"GIT_CONFIG_GLOBAL=/dev/null"}, }, @@ -192,10 +190,7 @@ func (r *Repository) CommitsByPage(ref *Reference, page, size int) (Commits, err } commits := make(Commits, len(cs)) for i, c := range cs { - commits[i] = &Commit{ - Commit: c, - Hash: Hash(c.ID.String()), - } + commits[i] = c } return commits, nil } diff --git a/git/tag.go b/git/tag.go new file mode 100644 index 000000000..b176a62db --- /dev/null +++ b/git/tag.go @@ -0,0 +1,5 @@ +package git + +import "github.com/gogs/git-module" + +type Tag = git.Tag diff --git a/server/ssh/cmd/blob.go b/server/ssh/cmd/blob.go index 5065bd1c6..48ba6544a 100644 --- a/server/ssh/cmd/blob.go +++ b/server/ssh/cmd/blob.go @@ -60,7 +60,7 @@ func blobCommand() *cobra.Command { if err != nil { return err } - ref = head.Hash.String() + ref = head.ID } tree, err := r.LsTree(ref) diff --git a/server/ssh/cmd/commit.go b/server/ssh/cmd/commit.go index f8ffaa1cf..75f02efbd 100644 --- a/server/ssh/cmd/commit.go +++ b/server/ssh/cmd/commit.go @@ -40,16 +40,11 @@ func commitCommand() *cobra.Command { return err } - rawCommit, err := r.CommitByRevision(commitSHA) + commit, err := r.CommitByRevision(commitSHA) if err != nil { return err } - commit := &git.Commit{ - Commit: rawCommit, - Hash: git.Hash(commitSHA), - } - patch, err := r.Patch(commit) if err != nil { return err diff --git a/server/ssh/cmd/tree.go b/server/ssh/cmd/tree.go index ccb72a70f..e380a348e 100644 --- a/server/ssh/cmd/tree.go +++ b/server/ssh/cmd/tree.go @@ -49,7 +49,7 @@ func treeCommand() *cobra.Command { return err } - ref = head.Hash.String() + ref = head.ID } tree, err := r.LsTree(ref) diff --git a/server/ui/pages/repo/refs.go b/server/ui/pages/repo/refs.go index d83bd336c..fb3dd0555 100644 --- a/server/ui/pages/repo/refs.go +++ b/server/ui/pages/repo/refs.go @@ -9,14 +9,13 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/soft-serve/git" - ggit "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/proto" "github.com/charmbracelet/soft-serve/server/ui/common" "github.com/charmbracelet/soft-serve/server/ui/components/selector" ) // RefMsg is a message that contains a git.Reference. -type RefMsg *ggit.Reference +type RefMsg *git.Reference // RefItemsMsg is a message that contains a list of RefItem. type RefItemsMsg struct { @@ -215,7 +214,19 @@ func (r *Refs) updateItemsCmd() tea.Msg { } for _, ref := range refs { if strings.HasPrefix(ref.Name().String(), r.refPrefix) { - its = append(its, RefItem{Reference: ref}) + refItem := RefItem{ + Reference: ref, + } + + if ref.IsTag() { + refItem.Tag, _ = rr.Tag(ref.Name().Short()) + if refItem.Tag != nil { + refItem.Commit, _ = refItem.Tag.Commit() + } + } else { + refItem.Commit, _ = rr.CatFileCommit(ref.ID) + } + its = append(its, refItem) } } sort.Sort(its) @@ -238,7 +249,7 @@ func (r *Refs) setItems(items []selector.IdentifiableItem) tea.Cmd { } } -func switchRefCmd(ref *ggit.Reference) tea.Cmd { +func switchRefCmd(ref *git.Reference) tea.Cmd { return func() tea.Msg { return RefMsg(ref) } diff --git a/server/ui/pages/repo/refsitem.go b/server/ui/pages/repo/refsitem.go index 0d552bf01..85e96857d 100644 --- a/server/ui/pages/repo/refsitem.go +++ b/server/ui/pages/repo/refsitem.go @@ -3,6 +3,8 @@ package repo import ( "fmt" "io" + "strings" + "time" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" @@ -10,11 +12,15 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/ui/common" + "github.com/dustin/go-humanize" + "github.com/muesli/reflow/truncate" ) // RefItem is a git reference item. type RefItem struct { *git.Reference + *git.Tag + *git.Commit } // ID implements selector.IdentifiableItem. @@ -51,7 +57,13 @@ func (cl RefItems) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] } // Less implements sort.Interface. func (cl RefItems) Less(i, j int) bool { - return cl[i].Short() < cl[j].Short() + if cl[i].Commit != nil && cl[j].Commit != nil { + return cl[i].Commit.Author.When.After(cl[j].Commit.Author.When) + } else if cl[i].Commit != nil && cl[j].Commit == nil { + return true + } else { + return false + } } // RefItemDelegate is the delegate for the ref item. @@ -83,46 +95,94 @@ func (d RefItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { // Render implements list.ItemDelegate. func (d RefItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { - s := d.common.Styles.Ref i, ok := listItem.(RefItem) if !ok { return } - var st lipgloss.Style - var selector string - isTag := i.Reference.IsTag() isActive := index == m.Index() + s := d.common.Styles.Ref + st := s.Normal + selector := " " + if isActive { + st = s.Active + selector = s.ItemSelector.String() + } + horizontalFrameSize := st.Base.GetHorizontalFrameSize() + var itemSt lipgloss.Style if isTag && isActive { - st = s.Active.ItemTag + itemSt = st.ItemTag } else if isTag { - st = s.Normal.ItemTag + itemSt = st.ItemTag } else if isActive { - st = s.Active.Item + itemSt = st.Item } else { - st = s.Normal.Item + itemSt = st.Item } - if isActive { - selector = s.ItemSelector.String() - } else { - selector = " " + var hash string + c := i.Commit + if c != nil { + hash = c.ID.String()[:7] } ref := i.Short() - ref = s.ItemBranch.Render(ref) - refMaxWidth := m.Width() - - s.ItemSelector.GetMarginLeft() - - s.ItemSelector.GetWidth() - - s.Normal.Item.GetMarginLeft() - ref = common.TruncateString(ref, refMaxWidth) - ref = st.Render(ref) + + var desc string + if isTag { + if c != nil { + date := c.Committer.When.Format("Jan 02") + if c.Committer.When.Year() != time.Now().Year() { + date += fmt.Sprintf(" %d", c.Committer.When.Year()) + } + desc += " " + st.ItemDesc.Render(date) + } + + t := i.Tag + if t != nil { + msgSt := st.ItemDesc.Copy().Faint(false) + msg := t.Message() + nl := strings.Index(msg, "\n") + if nl > 0 { + msg = msg[:nl] + } + msg = strings.TrimSpace(msg) + if msg != "" { + msg = common.TruncateString(msg, m.Width()- + horizontalFrameSize- + lipgloss.Width(selector)- + lipgloss.Width(ref)- + lipgloss.Width(hash)- + lipgloss.Width(desc)-3) // 3 is for the paddings and truncation symbol + desc = " " + msgSt.Render(msg) + desc + } + } + } else if c != nil { + on := "updated " + humanize.Time(c.Committer.When) + desc += " " + st.ItemDesc.Render(on) + } + + ref = itemSt.Render(ref) + hash = st.ItemHash.Copy(). + Align(lipgloss.Right). + PaddingLeft(1). + Width(m.Width() - + horizontalFrameSize - + lipgloss.Width(selector) - + lipgloss.Width(ref) - + lipgloss.Width(desc) - 1). // 1 is for the left padding + Render(hash) fmt.Fprint(w, d.common.Zone.Mark( i.ID(), - fmt.Sprint(selector, ref), + st.Base.Render( + lipgloss.JoinVertical(lipgloss.Top, + truncate.String(selector+ref+desc+hash, + uint(m.Width()-horizontalFrameSize)), + ), + ), ), ) } diff --git a/server/ui/styles/styles.go b/server/ui/styles/styles.go index 63805bbbe..39fe5211c 100644 --- a/server/ui/styles/styles.go +++ b/server/ui/styles/styles.go @@ -89,15 +89,20 @@ type Styles struct { Ref struct { Normal struct { - Item lipgloss.Style - ItemTag lipgloss.Style + Base lipgloss.Style + Item lipgloss.Style + ItemTag lipgloss.Style + ItemDesc lipgloss.Style + ItemHash lipgloss.Style } Active struct { - Item lipgloss.Style - ItemTag lipgloss.Style + Base lipgloss.Style + Item lipgloss.Style + ItemTag lipgloss.Style + ItemDesc lipgloss.Style + ItemHash lipgloss.Style } ItemSelector lipgloss.Style - ItemBranch lipgloss.Style Paginator lipgloss.Style } @@ -348,7 +353,9 @@ func DefaultStyles() *Styles { s.Ref.Active.Item = lipgloss.NewStyle(). Foreground(highlightColorDim) - s.Ref.ItemBranch = lipgloss.NewStyle() + s.Ref.Normal.Base = lipgloss.NewStyle() + + s.Ref.Active.Base = lipgloss.NewStyle() s.Ref.Normal.ItemTag = lipgloss.NewStyle(). Foreground(lipgloss.Color("39")) @@ -361,6 +368,21 @@ func DefaultStyles() *Styles { Bold(true). Foreground(highlightColor) + s.Ref.Normal.ItemDesc = lipgloss.NewStyle(). + Faint(true) + + s.Ref.Active.ItemDesc = lipgloss.NewStyle(). + Foreground(highlightColor). + Faint(true) + + s.Ref.Normal.ItemHash = lipgloss.NewStyle(). + Foreground(hashColor). + Bold(true) + + s.Ref.Active.ItemHash = lipgloss.NewStyle(). + Foreground(highlightColor). + Bold(true) + s.Ref.Paginator = s.Log.Paginator.Copy() s.Tree.Selector = s.Tree.Normal.FileName.Copy(). From 60c19818216f98154b498a45c5ade51c10e550d2 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 5 Oct 2023 18:33:46 -0400 Subject: [PATCH 09/12] fix(ui): clean up statusbar logic --- server/ui/components/statusbar/statusbar.go | 47 +++++++++------- server/ui/pages/repo/files.go | 58 +++++++++++--------- server/ui/pages/repo/log.go | 9 ++- server/ui/pages/repo/readme.go | 10 ++-- server/ui/pages/repo/refs.go | 1 + server/ui/pages/repo/repo.go | 61 +++++++++++---------- 6 files changed, 104 insertions(+), 82 deletions(-) diff --git a/server/ui/components/statusbar/statusbar.go b/server/ui/components/statusbar/statusbar.go index 1dc927c11..f60e07795 100644 --- a/server/ui/components/statusbar/statusbar.go +++ b/server/ui/components/statusbar/statusbar.go @@ -7,16 +7,8 @@ import ( "github.com/muesli/reflow/truncate" ) -// StatusBarMsg is a message sent to the status bar. -type StatusBarMsg struct { //nolint:revive - Key string - Value string - Info string - Extra string -} - -// StatusBar is a status bar model. -type StatusBar struct { +// Model is a status bar model. +type Model struct { common common.Common key string value string @@ -25,38 +17,51 @@ type StatusBar struct { } // New creates a new status bar component. -func New(c common.Common) *StatusBar { - s := &StatusBar{ +func New(c common.Common) *Model { + s := &Model{ common: c, } return s } // SetSize implements common.Component. -func (s *StatusBar) SetSize(width, height int) { +func (s *Model) SetSize(width, height int) { s.common.Width = width s.common.Height = height } +// SetStatus sets the status bar status. +func (s *Model) SetStatus(key, value, info, extra string) { + if key != "" { + s.key = key + } + if value != "" { + s.value = value + } + if info != "" { + s.info = info + } + if extra != "" { + s.extra = extra + } +} + // Init implements tea.Model. -func (s *StatusBar) Init() tea.Cmd { +func (s *Model) Init() tea.Cmd { return nil } // Update implements tea.Model. -func (s *StatusBar) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (s *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case StatusBarMsg: - s.key = msg.Key - s.value = msg.Value - s.info = msg.Info - s.extra = msg.Extra + case tea.WindowSizeMsg: + s.SetSize(msg.Width, msg.Height) } return s, nil } // View implements tea.Model. -func (s *StatusBar) View() string { +func (s *Model) View() string { st := s.common.Styles w := lipgloss.Width help := s.common.Zone.Mark( diff --git a/server/ui/pages/repo/files.go b/server/ui/pages/repo/files.go index 6968ca897..d05be2978 100644 --- a/server/ui/pages/repo/files.go +++ b/server/ui/pages/repo/files.go @@ -203,7 +203,7 @@ func (f *Files) Init() tea.Cmd { f.currentItem = nil f.activeView = filesViewLoading f.lastSelected = make([]int, 0) - return tea.Batch(f.spinner.Tick, f.updateFilesCmd()) + return tea.Batch(f.spinner.Tick, f.updateFilesCmd) } // Update implements tea.Model. @@ -219,6 +219,7 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case FileItemsMsg: cmds = append(cmds, f.selector.SetItems(msg), + updateStatusBarCmd, ) f.activeView = filesViewFiles if f.cursor >= 0 { @@ -228,7 +229,10 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case FileContentMsg: f.activeView = filesViewContent f.currentContent = msg - f.code.SetContent(msg.content, msg.ext) + cmds = append(cmds, + f.code.SetContent(msg.content, msg.ext), + updateStatusBarCmd, + ) f.code.GotoTop() case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { @@ -236,7 +240,7 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.currentItem = &sel f.path = filepath.Join(f.path, sel.entry.Name()) if sel.entry.IsTree() { - cmds = append(cmds, f.selectTreeCmd()) + cmds = append(cmds, f.selectTreeCmd) } else { cmds = append(cmds, f.selectFileCmd) } @@ -269,7 +273,7 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch f.activeView { case filesViewFiles: if f.repo != nil { - cmds = append(cmds, f.updateFilesCmd()) + cmds = append(cmds, f.updateFilesCmd) } case filesViewContent: if f.currentContent.content != "" { @@ -280,6 +284,8 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } + case selector.ActiveMsg: + cmds = append(cmds, updateStatusBarCmd) case EmptyRepoMsg: f.ref = nil f.path = "" @@ -336,8 +342,8 @@ func (f *Files) SpinnerID() int { // StatusBarValue returns the status bar value. func (f *Files) StatusBarValue() string { p := f.path - if p == "." { - return "" + if p == "." || p == "" { + return " " } return p } @@ -354,7 +360,7 @@ func (f *Files) StatusBarInfo() string { } } -func (f *Files) updateFilesCmd() tea.Cmd { +func (f *Files) updateFilesCmd() tea.Msg { files := make([]selector.IdentifiableItem, 0) dirs := make([]selector.IdentifiableItem, 0) if f.ref == nil { @@ -366,34 +372,32 @@ func (f *Files) updateFilesCmd() tea.Cmd { } path := f.path ref := f.ref - return func() tea.Msg { - t, err := r.TreePath(ref, path) - if err != nil { - return common.ErrorCmd(err) - } - ents, err := t.Entries() - if err != nil { - return common.ErrorCmd(err) - } - ents.Sort() - for _, e := range ents { - if e.IsTree() { - dirs = append(dirs, FileItem{entry: e}) - } else { - files = append(files, FileItem{entry: e}) - } + t, err := r.TreePath(ref, path) + if err != nil { + return common.ErrorCmd(err) + } + ents, err := t.Entries() + if err != nil { + return common.ErrorCmd(err) + } + ents.Sort() + for _, e := range ents { + if e.IsTree() { + dirs = append(dirs, FileItem{entry: e}) + } else { + files = append(files, FileItem{entry: e}) } - return FileItemsMsg(append(dirs, files...)) } + return FileItemsMsg(append(dirs, files...)) } -func (f *Files) selectTreeCmd() tea.Cmd { +func (f *Files) selectTreeCmd() tea.Msg { if f.currentItem != nil && f.currentItem.entry.IsTree() { f.lastSelected = append(f.lastSelected, f.selector.Index()) f.cursor = 0 return f.updateFilesCmd() } - return common.ErrorCmd(errNoFileSelected) + return common.ErrorMsg(errNoFileSelected) } func (f *Files) selectFileCmd() tea.Msg { @@ -455,7 +459,7 @@ func (f *Files) deselectItemCmd() tea.Cmd { } f.cursor = index f.activeView = filesViewFiles - return f.updateFilesCmd() + return f.updateFilesCmd } func (f *Files) setItems(items []selector.IdentifiableItem) tea.Cmd { diff --git a/server/ui/pages/repo/log.go b/server/ui/pages/repo/log.go index e8666e4bf..c010264dc 100644 --- a/server/ui/pages/repo/log.go +++ b/server/ui/pages/repo/log.go @@ -211,6 +211,7 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if i != nil { l.activeCommit = i.(LogItem).Commit } + cmds = append(cmds, updateStatusBarCmd) case tea.KeyMsg, tea.MouseMsg: switch l.activeView { case logViewCommits: @@ -249,12 +250,14 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if l.activeView == logViewDiff { l.activeView = logViewCommits l.selectedCommit = nil + cmds = append(cmds, updateStatusBarCmd) } case selector.ActiveMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: l.activeCommit = sel.Commit } + cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: @@ -277,6 +280,7 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ) l.vp.GotoTop() l.activeView = logViewDiff + cmds = append(cmds, updateStatusBarCmd) case footer.ToggleFooterMsg: cmds = append(cmds, l.updateCommitsCmd) case tea.WindowSizeMsg: @@ -305,7 +309,10 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { l.activeCommit = nil l.selectedCommit = nil l.selector.Select(0) - cmds = append(cmds, l.setItems([]selector.IdentifiableItem{})) + cmds = append(cmds, + l.setItems([]selector.IdentifiableItem{}), + updateStatusBarCmd, + ) case spinner.TickMsg: if l.activeView == logViewLoading && l.spinner.ID() == msg.ID { s, cmd := l.spinner.Update(msg) diff --git a/server/ui/pages/repo/readme.go b/server/ui/pages/repo/readme.go index b76dcf82c..396a6d7de 100644 --- a/server/ui/pages/repo/readme.go +++ b/server/ui/pages/repo/readme.go @@ -99,8 +99,10 @@ func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) { r.ref = msg cmds = append(cmds, r.Init()) case EmptyRepoMsg: - r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(), - r.repo.Name()), ".md") + cmds = append(cmds, + r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(), + r.repo.Name()), ".md"), + ) case ReadmeMsg: r.isLoading = false r.readmePath = msg.Path @@ -139,8 +141,8 @@ func (r *Readme) SpinnerID() int { // StatusBarValue implements statusbar.StatusBar. func (r *Readme) StatusBarValue() string { dir := filepath.Dir(r.readmePath) - if dir == "." { - return "" + if dir == "." || dir == "" { + return " " } return dir } diff --git a/server/ui/pages/repo/refs.go b/server/ui/pages/repo/refs.go index fb3dd0555..125e940f7 100644 --- a/server/ui/pages/repo/refs.go +++ b/server/ui/pages/repo/refs.go @@ -138,6 +138,7 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case RefItem: r.activeRef = sel.Reference } + cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch i := msg.IdentifiableItem.(type) { case RefItem: diff --git a/server/ui/pages/repo/repo.go b/server/ui/pages/repo/repo.go index 06bdaa738..1c6f2e298 100644 --- a/server/ui/pages/repo/repo.go +++ b/server/ui/pages/repo/repo.go @@ -44,13 +44,16 @@ type CopyMsg struct { Message string } +// SwitchTabMsg is a message to switch tabs. +type SwitchTabMsg common.TabComponent + // Repo is a view for a git repository. type Repo struct { common common.Common selectedRepo proto.Repository activeTab int tabs *tabs.Tabs - statusbar *statusbar.StatusBar + statusbar *statusbar.Model panes []common.TabComponent ref *git.Reference state state @@ -125,7 +128,6 @@ func (r *Repo) FullHelp() [][]key.Binding { // Init implements tea.View. func (r *Repo) Init() tea.Cmd { r.state = loadingState - // r.panesReady = make([]bool, len(r.panes)) r.activeTab = 0 return tea.Batch( r.tabs.Init(), @@ -146,12 +148,12 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // This will set the selected repo in each pane's model. r.updateModels(msg), ) + r.setStatusBarInfo() case RefMsg: r.ref = msg - cmds = append(cmds, - r.updateModels(msg), - ) + cmds = append(cmds, r.updateModels(msg)) r.state = readyState + r.setStatusBarInfo() case tabs.SelectTabMsg: r.activeTab = int(msg) t, cmd := r.tabs.Update(msg) @@ -161,6 +163,7 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case tabs.ActiveTabMsg: r.activeTab = int(msg) + r.setStatusBarInfo() case tea.KeyMsg, tea.MouseMsg: t, cmd := r.tabs.Update(msg) r.tabs = t.(*tabs.Tabs) @@ -189,21 +192,18 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } + r.setStatusBarInfo() case CopyMsg: txt := msg.Text if cfg := r.common.Config(); cfg != nil { r.common.Output.Copy(txt) } - cmds = append(cmds, func() tea.Msg { - return statusbar.StatusBarMsg{ - Value: msg.Message, - } - }) + r.statusbar.SetStatus("", msg.Message, "", "") case ReadmeMsg: cmds = append(cmds, r.updateTabComponent(&Readme{}, msg)) - case FileItemsMsg: + case FileItemsMsg, FileContentMsg: cmds = append(cmds, r.updateTabComponent(&Files{}, msg)) - case LogCountMsg, LogItemsMsg: + case LogItemsMsg, LogDiffMsg, LogCountMsg: cmds = append(cmds, r.updateTabComponent(&Log{}, msg)) case RefItemsMsg: cmds = append(cmds, r.updateTabComponent(&Refs{refPrefix: msg.prefix}, msg)) @@ -245,6 +245,8 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } } + case UpdateStatusBarMsg: + r.setStatusBarInfo() } active := r.panes[r.activeTab] m, cmd := active.Update(msg) @@ -253,12 +255,10 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } - if r.selectedRepo != nil { - s, cmd := r.statusbar.Update(r.makeStatusBarMsg()) - r.statusbar = s.(*statusbar.StatusBar) - if cmd != nil { - cmds = append(cmds, cmd) - } + s, cmd := r.statusbar.Update(msg) + r.statusbar = s.(*statusbar.Model) + if cmd != nil { + cmds = append(cmds, cmd) } return r, tea.Batch(cmds...) @@ -341,20 +341,21 @@ func (r *Repo) headerView() string { ) } -func (r *Repo) makeStatusBarMsg() statusbar.StatusBarMsg { +func (r *Repo) setStatusBarInfo() { + if r.selectedRepo == nil { + return + } + active := r.panes[r.activeTab] + key := r.selectedRepo.Name() value := active.StatusBarValue() info := active.StatusBarInfo() - branch := "*" + extra := "*" if r.ref != nil { - branch += " " + r.ref.Name().Short() - } - return statusbar.StatusBarMsg{ - Key: r.selectedRepo.Name(), - Value: value, - Info: info, - Extra: branch, + extra += " " + r.ref.Name().Short() } + + r.statusbar.SetStatus(key, value, info, extra) } func (r *Repo) updateTabComponent(c common.TabComponent, msg tea.Msg) tea.Cmd { @@ -397,14 +398,16 @@ func backCmd() tea.Msg { return BackMsg{} } -type SwitchTabMsg common.TabComponent - func switchTabCmd(m common.TabComponent) tea.Cmd { return func() tea.Msg { return SwitchTabMsg(m) } } +func updateStatusBarCmd() tea.Msg { + return UpdateStatusBarMsg{} +} + func renderLoading(c common.Common, s spinner.Model) string { msg := fmt.Sprintf("%s loading…", s.View()) return c.Styles.SpinnerContainer.Copy(). From 0f8dc1183680618e57698e987e4dd47c98939b73 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 6 Oct 2023 12:24:40 -0400 Subject: [PATCH 10/12] fix(ui): cleanup statusbar and misc msgs --- cmd/soft/browse.go | 40 +++++++++++--------------------- server/ssh/ui.go | 4 ++-- server/ui/pages/repo/files.go | 13 ++++------- server/ui/pages/repo/log.go | 22 +++++++++--------- server/ui/pages/repo/readme.go | 2 ++ server/ui/pages/repo/refs.go | 3 ++- server/ui/pages/repo/refsitem.go | 2 +- server/ui/pages/repo/repo.go | 36 ++++++++++++++-------------- server/ui/styles/styles.go | 3 +++ 9 files changed, 56 insertions(+), 69 deletions(-) diff --git a/cmd/soft/browse.go b/cmd/soft/browse.go index 9b346b417..009c4de62 100644 --- a/cmd/soft/browse.go +++ b/cmd/soft/browse.go @@ -1,8 +1,6 @@ package main import ( - "io" - "os" "path/filepath" "time" @@ -208,22 +206,20 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View implements tea.Model. func (m *model) View() string { - view := m.m.View() + style := m.c.Styles.App.Copy() + wm, hm := style.GetHorizontalFrameSize(), style.GetVerticalFrameSize() if m.showFooter { - view = lipgloss.JoinVertical(lipgloss.Left, view, m.f.View()) + hm += m.f.Height() } + var view string switch m.state { + case startState: + view = m.m.View() case errorState: - appStyle := m.c.Styles.App.Copy() - wm, hm := appStyle.GetHorizontalFrameSize(), appStyle.GetVerticalFrameSize() - if m.showFooter { - hm += m.f.Height() - } - err := m.c.Styles.ErrorTitle.Render("Bummer") err += m.c.Styles.ErrorBody.Render(m.error.Error()) - return m.c.Styles.Error.Copy(). + view = m.c.Styles.Error.Copy(). Width(m.c.Width - wm - m.c.Styles.ErrorBody.GetHorizontalFrameSize()). @@ -231,9 +227,13 @@ func (m *model) View() string { hm - m.c.Styles.Error.GetVerticalFrameSize()). Render(err) - default: - return m.c.Zone.Scan(m.c.Styles.App.Render(view)) } + + if m.showFooter { + view = lipgloss.JoinVertical(lipgloss.Top, view, m.f.View()) + } + + return m.c.Zone.Scan(style.Render(view)) } type repository struct { @@ -244,19 +244,7 @@ var _ proto.Repository = repository{} // Description implements proto.Repository. func (r repository) Description() string { - fp := filepath.Join(r.r.Path, "description") - f, err := os.Open(fp) - if err != nil { - return "" - } - - defer f.Close() // nolint: errcheck - bts, err := io.ReadAll(f) - if err != nil { - return "" - } - - return string(bts) + return "" } // ID implements proto.Repository. diff --git a/server/ssh/ui.go b/server/ssh/ui.go index 329e95c8d..87c73ad71 100644 --- a/server/ssh/ui.go +++ b/server/ssh/ui.go @@ -280,10 +280,10 @@ func (ui *UI) View() string { view = "Unknown state :/ this is a bug!" } if ui.activePage == selectionPage { - view = lipgloss.JoinVertical(lipgloss.Left, ui.header.View(), view) + view = lipgloss.JoinVertical(lipgloss.Top, ui.header.View(), view) } if ui.showFooter { - view = lipgloss.JoinVertical(lipgloss.Left, view, ui.footer.View()) + view = lipgloss.JoinVertical(lipgloss.Top, view, ui.footer.View()) } return ui.common.Zone.Scan( ui.common.Styles.App.Render(view), diff --git a/server/ui/pages/repo/files.go b/server/ui/pages/repo/files.go index d05be2978..5103baacd 100644 --- a/server/ui/pages/repo/files.go +++ b/server/ui/pages/repo/files.go @@ -219,7 +219,6 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case FileItemsMsg: cmds = append(cmds, f.selector.SetItems(msg), - updateStatusBarCmd, ) f.activeView = filesViewFiles if f.cursor >= 0 { @@ -231,7 +230,6 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.currentContent = msg cmds = append(cmds, f.code.SetContent(msg.content, msg.ext), - updateStatusBarCmd, ) f.code.GotoTop() case selector.SelectMsg: @@ -245,9 +243,6 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, f.selectFileCmd) } } - case BackMsg: - f.path = filepath.Dir(f.path) - cmds = append(cmds, f.deselectItemCmd()) case tea.KeyMsg: switch f.activeView { case filesViewFiles: @@ -255,12 +250,12 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, f.common.KeyMap.SelectItem): cmds = append(cmds, f.selector.SelectItemCmd) case key.Matches(msg, f.common.KeyMap.BackItem): - cmds = append(cmds, backCmd) + cmds = append(cmds, f.deselectItemCmd()) } case filesViewContent: switch { case key.Matches(msg, f.common.KeyMap.BackItem): - cmds = append(cmds, backCmd) + cmds = append(cmds, f.deselectItemCmd()) case key.Matches(msg, f.common.KeyMap.Copy): cmds = append(cmds, copyCmd(f.currentContent.content, "File contents copied to clipboard")) case key.Matches(msg, lineNo): @@ -270,6 +265,7 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case tea.WindowSizeMsg: + f.SetSize(msg.Width, msg.Height) switch f.activeView { case filesViewFiles: if f.repo != nil { @@ -284,8 +280,6 @@ func (f *Files) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } } - case selector.ActiveMsg: - cmds = append(cmds, updateStatusBarCmd) case EmptyRepoMsg: f.ref = nil f.path = "" @@ -452,6 +446,7 @@ func (f *Files) selectFileCmd() tea.Msg { } func (f *Files) deselectItemCmd() tea.Cmd { + f.path = filepath.Dir(f.path) index := 0 if len(f.lastSelected) > 0 { index = f.lastSelected[len(f.lastSelected)-1] diff --git a/server/ui/pages/repo/log.go b/server/ui/pages/repo/log.go index c010264dc..ede919630 100644 --- a/server/ui/pages/repo/log.go +++ b/server/ui/pages/repo/log.go @@ -211,7 +211,6 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if i != nil { l.activeCommit = i.(LogItem).Commit } - cmds = append(cmds, updateStatusBarCmd) case tea.KeyMsg, tea.MouseMsg: switch l.activeView { case logViewCommits: @@ -242,22 +241,17 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(kmsg, l.common.KeyMap.BackItem): - cmds = append(cmds, backCmd) + l.goBack() } } } - case BackMsg: - if l.activeView == logViewDiff { - l.activeView = logViewCommits - l.selectedCommit = nil - cmds = append(cmds, updateStatusBarCmd) - } + case GoBackMsg: + l.goBack() case selector.ActiveMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: l.activeCommit = sel.Commit } - cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch sel := msg.IdentifiableItem.(type) { case LogItem: @@ -280,10 +274,10 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { ) l.vp.GotoTop() l.activeView = logViewDiff - cmds = append(cmds, updateStatusBarCmd) case footer.ToggleFooterMsg: cmds = append(cmds, l.updateCommitsCmd) case tea.WindowSizeMsg: + l.SetSize(msg.Width, msg.Height) if l.selectedCommit != nil && l.currentDiff != nil { l.vp.SetContent( lipgloss.JoinVertical(lipgloss.Top, @@ -311,7 +305,6 @@ func (l *Log) Update(msg tea.Msg) (tea.Model, tea.Cmd) { l.selector.Select(0) cmds = append(cmds, l.setItems([]selector.IdentifiableItem{}), - updateStatusBarCmd, ) case spinner.TickMsg: if l.activeView == logViewLoading && l.spinner.ID() == msg.ID { @@ -401,6 +394,13 @@ func (l *Log) StatusBarInfo() string { } } +func (l *Log) goBack() { + if l.activeView == logViewDiff { + l.activeView = logViewCommits + l.selectedCommit = nil + } +} + func (l *Log) countCommitsCmd() tea.Msg { if l.ref == nil { return nil diff --git a/server/ui/pages/repo/readme.go b/server/ui/pages/repo/readme.go index 396a6d7de..a1d8218d4 100644 --- a/server/ui/pages/repo/readme.go +++ b/server/ui/pages/repo/readme.go @@ -98,6 +98,8 @@ func (r *Readme) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case RefMsg: r.ref = msg cmds = append(cmds, r.Init()) + case tea.WindowSizeMsg: + r.SetSize(msg.Width, msg.Height) case EmptyRepoMsg: cmds = append(cmds, r.code.SetContent(defaultEmptyRepoMsg(r.common.Config(), diff --git a/server/ui/pages/repo/refs.go b/server/ui/pages/repo/refs.go index 125e940f7..3b7be378a 100644 --- a/server/ui/pages/repo/refs.go +++ b/server/ui/pages/repo/refs.go @@ -124,6 +124,8 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case RefMsg: r.ref = msg cmds = append(cmds, r.Init()) + case tea.WindowSizeMsg: + r.SetSize(msg.Width, msg.Height) case RefItemsMsg: if r.refPrefix == msg.prefix { cmds = append(cmds, r.selector.SetItems(msg.items)) @@ -138,7 +140,6 @@ func (r *Refs) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case RefItem: r.activeRef = sel.Reference } - cmds = append(cmds, updateStatusBarCmd) case selector.SelectMsg: switch i := msg.IdentifiableItem.(type) { case RefItem: diff --git a/server/ui/pages/repo/refsitem.go b/server/ui/pages/repo/refsitem.go index 85e96857d..333ffc393 100644 --- a/server/ui/pages/repo/refsitem.go +++ b/server/ui/pages/repo/refsitem.go @@ -178,7 +178,7 @@ func (d RefItemDelegate) Render(w io.Writer, m list.Model, index int, listItem l d.common.Zone.Mark( i.ID(), st.Base.Render( - lipgloss.JoinVertical(lipgloss.Top, + lipgloss.JoinHorizontal(lipgloss.Top, truncate.String(selector+ref+desc+hash, uint(m.Width()-horizontalFrameSize)), ), diff --git a/server/ui/pages/repo/repo.go b/server/ui/pages/repo/repo.go index 1c6f2e298..de3f27110 100644 --- a/server/ui/pages/repo/repo.go +++ b/server/ui/pages/repo/repo.go @@ -2,6 +2,7 @@ package repo import ( "fmt" + "strings" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -12,6 +13,7 @@ import ( "github.com/charmbracelet/soft-serve/server/proto" "github.com/charmbracelet/soft-serve/server/ui/common" "github.com/charmbracelet/soft-serve/server/ui/components/footer" + "github.com/charmbracelet/soft-serve/server/ui/components/selector" "github.com/charmbracelet/soft-serve/server/ui/components/statusbar" "github.com/charmbracelet/soft-serve/server/ui/components/tabs" ) @@ -29,14 +31,11 @@ type EmptyRepoMsg struct{} // CopyURLMsg is a message to copy the URL of the current repository. type CopyURLMsg struct{} -// UpdateStatusBarMsg updates the status bar. -type UpdateStatusBarMsg struct{} - // RepoMsg is a message that contains a git.Repository. type RepoMsg proto.Repository // nolint:revive -// BackMsg is a message to go back to the previous view. -type BackMsg struct{} +// GoBackMsg is a message to go back to the previous view. +type GoBackMsg struct{} // CopyMsg is a message to indicate copied text. type CopyMsg struct { @@ -148,12 +147,10 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // This will set the selected repo in each pane's model. r.updateModels(msg), ) - r.setStatusBarInfo() case RefMsg: r.ref = msg cmds = append(cmds, r.updateModels(msg)) r.state = readyState - r.setStatusBarInfo() case tabs.SelectTabMsg: r.activeTab = int(msg) t, cmd := r.tabs.Update(msg) @@ -163,7 +160,6 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case tabs.ActiveTabMsg: r.activeTab = int(msg) - r.setStatusBarInfo() case tea.KeyMsg, tea.MouseMsg: t, cmd := r.tabs.Update(msg) r.tabs = t.(*tabs.Tabs) @@ -188,11 +184,10 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.MouseRight: switch { case r.common.Zone.Get("repo-main").InBounds(msg): - cmds = append(cmds, backCmd) + cmds = append(cmds, goBackCmd) } } } - r.setStatusBarInfo() case CopyMsg: txt := msg.Text if cfg := r.common.Config(); cfg != nil { @@ -245,8 +240,6 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { break } } - case UpdateStatusBarMsg: - r.setStatusBarInfo() } active := r.panes[r.activeTab] m, cmd := active.Update(msg) @@ -255,6 +248,15 @@ func (r *Repo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } + // Update the status bar on these events + // Must come after we've updated the active tab + switch msg.(type) { + case RepoMsg, RefMsg, tabs.ActiveTabMsg, tea.KeyMsg, tea.MouseMsg, + FileItemsMsg, FileContentMsg, selector.ActiveMsg, LogItemsMsg, + GoBackMsg, LogDiffMsg, EmptyRepoMsg: + r.setStatusBarInfo() + } + s, cmd := r.statusbar.Update(msg) r.statusbar = s.(*statusbar.Model) if cmd != nil { @@ -310,7 +312,7 @@ func (r *Repo) headerView() string { name = r.selectedRepo.Name() } name = r.common.Styles.Repo.HeaderName.Render(name) - desc := r.selectedRepo.Description() + desc := strings.TrimSpace(r.selectedRepo.Description()) if desc == "" { desc = name name = "" @@ -394,8 +396,8 @@ func copyCmd(text, msg string) tea.Cmd { } } -func backCmd() tea.Msg { - return BackMsg{} +func goBackCmd() tea.Msg { + return GoBackMsg{} } func switchTabCmd(m common.TabComponent) tea.Cmd { @@ -404,10 +406,6 @@ func switchTabCmd(m common.TabComponent) tea.Cmd { } } -func updateStatusBarCmd() tea.Msg { - return UpdateStatusBarMsg{} -} - func renderLoading(c common.Common, s spinner.Model) string { msg := fmt.Sprintf("%s loading…", s.View()) return c.Styles.SpinnerContainer.Copy(). diff --git a/server/ui/styles/styles.go b/server/ui/styles/styles.go index 39fe5211c..1e2eaef0f 100644 --- a/server/ui/styles/styles.go +++ b/server/ui/styles/styles.go @@ -104,6 +104,7 @@ type Styles struct { } ItemSelector lipgloss.Style Paginator lipgloss.Style + Selector lipgloss.Style } Tree struct { @@ -385,6 +386,8 @@ func DefaultStyles() *Styles { s.Ref.Paginator = s.Log.Paginator.Copy() + s.Ref.Selector = lipgloss.NewStyle() + s.Tree.Selector = s.Tree.Normal.FileName.Copy(). Width(1). Foreground(selectorColor) From 43b4331f88ffd395098aa4332deeafa4cd9aa3fb Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 6 Oct 2023 12:49:27 -0400 Subject: [PATCH 11/12] fix(ui): preserve header line when no description is available --- server/ui/pages/repo/repo.go | 53 +++++++++++++++++++----------------- server/ui/styles/styles.go | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/server/ui/pages/repo/repo.go b/server/ui/pages/repo/repo.go index de3f27110..9d9403fc6 100644 --- a/server/ui/pages/repo/repo.go +++ b/server/ui/pages/repo/repo.go @@ -84,13 +84,22 @@ func New(c common.Common, comps ...common.TabComponent) *Repo { return r } -// SetSize implements common.Component. -func (r *Repo) SetSize(width, height int) { - r.common.SetSize(width, height) +func (r *Repo) getMargins() (w, h int) { + hh := 1 + if r.selectedRepo != nil && r.selectedRepo.Description() != "" { + hh++ + } hm := r.common.Styles.Repo.Body.GetVerticalFrameSize() + - r.common.Styles.Repo.Header.GetHeight() + + hh + r.common.Styles.Repo.Header.GetVerticalFrameSize() + r.common.Styles.StatusBar.GetHeight() + return 0, hm +} + +// SetSize implements common.Component. +func (r *Repo) SetSize(width, height int) { + r.common.SetSize(width, height) + _, hm := r.getMargins() r.tabs.SetSize(width, height-hm) r.statusbar.SetSize(width, height-hm) for _, p := range r.panes { @@ -272,11 +281,8 @@ func (r *Repo) View() string { Width(r.common.Width). Height(r.common.Height) repoBodyStyle := r.common.Styles.Repo.Body.Copy() - hm := repoBodyStyle.GetVerticalFrameSize() + - r.common.Styles.Repo.Header.GetHeight() + - r.common.Styles.Repo.Header.GetVerticalFrameSize() + - r.common.Styles.StatusBar.GetHeight() + - r.common.Styles.Tabs.GetHeight() + + _, hm := r.getMargins() + hm += r.common.Styles.Tabs.GetHeight() + r.common.Styles.Tabs.GetVerticalFrameSize() mainStyle := repoBodyStyle. Height(r.common.Height - hm) @@ -307,17 +313,17 @@ func (r *Repo) headerView() string { return "" } truncate := lipgloss.NewStyle().MaxWidth(r.common.Width) - name := r.selectedRepo.ProjectName() - if name == "" { - name = r.selectedRepo.Name() + header := r.selectedRepo.ProjectName() + if header == "" { + header = r.selectedRepo.Name() } - name = r.common.Styles.Repo.HeaderName.Render(name) + header = r.common.Styles.Repo.HeaderName.Render(header) desc := strings.TrimSpace(r.selectedRepo.Description()) - if desc == "" { - desc = name - name = "" - } else { - desc = r.common.Styles.Repo.HeaderDesc.Render(desc) + if desc != "" { + header = lipgloss.JoinVertical(lipgloss.Top, + header, + r.common.Styles.Repo.HeaderDesc.Render(desc), + ) } urlStyle := r.common.Styles.URLStyle.Copy(). Width(r.common.Width - lipgloss.Width(desc) - 1). @@ -331,15 +337,12 @@ func (r *Repo) headerView() string { fmt.Sprintf("%s-url", r.selectedRepo.Name()), urlStyle.Render(url), ) + + header = lipgloss.JoinHorizontal(lipgloss.Left, header, url) + style := r.common.Styles.Repo.Header.Copy().Width(r.common.Width) return style.Render( - lipgloss.JoinVertical(lipgloss.Top, - truncate.Render(name), - truncate.Render(lipgloss.JoinHorizontal(lipgloss.Left, - desc, - url, - )), - ), + truncate.Render(header), ) } diff --git a/server/ui/styles/styles.go b/server/ui/styles/styles.go index 1e2eaef0f..b71d5d9ba 100644 --- a/server/ui/styles/styles.go +++ b/server/ui/styles/styles.go @@ -233,7 +233,7 @@ func DefaultStyles() *Styles { Margin(1, 0) s.Repo.Header = lipgloss.NewStyle(). - Height(2). + MaxHeight(2). Border(lipgloss.NormalBorder(), false, false, true, false). BorderForeground(lipgloss.Color("236")) From 772130eb47a2ba6d7316ff8135e9f2e4a237c607 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 16 Oct 2023 14:12:38 -0400 Subject: [PATCH 12/12] fix(ui): match readme and list missing items styles --- server/ui/components/code/code.go | 2 +- server/ui/components/selector/selector.go | 2 +- server/ui/styles/styles.go | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/server/ui/components/code/code.go b/server/ui/components/code/code.go index aff1a5265..65abf9d1e 100644 --- a/server/ui/components/code/code.go +++ b/server/ui/components/code/code.go @@ -47,7 +47,7 @@ func New(c common.Common, content, extension string) *Code { content: content, extension: extension, Viewport: vp.New(c), - NoContentStyle: c.Styles.NoContent.Copy(), + NoContentStyle: c.Styles.NoContent.Copy().SetString("No Content."), LineDigitStyle: lineDigitStyle, LineBarStyle: lineBarStyle, } diff --git a/server/ui/components/selector/selector.go b/server/ui/components/selector/selector.go index f55c7a6c6..12d156a27 100644 --- a/server/ui/components/selector/selector.go +++ b/server/ui/components/selector/selector.go @@ -48,7 +48,7 @@ func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) itms[i] = item } l := list.New(itms, delegate, common.Width, common.Height) - l.Styles.NoItems = common.Styles.NoItems + l.Styles.NoItems = common.Styles.NoContent s := &Selector{ Model: &l, common: common, diff --git a/server/ui/styles/styles.go b/server/ui/styles/styles.go index b71d5d9ba..7e975ef5d 100644 --- a/server/ui/styles/styles.go +++ b/server/ui/styles/styles.go @@ -130,8 +130,6 @@ type Styles struct { NoContent lipgloss.Style - NoItems lipgloss.Style - StatusBar lipgloss.Style StatusBarKey lipgloss.Style StatusBarValue lipgloss.Style @@ -430,15 +428,10 @@ func DefaultStyles() *Styles { s.SpinnerContainer = lipgloss.NewStyle() s.NoContent = lipgloss.NewStyle(). - SetString("No Content."). MarginTop(1). MarginLeft(2). Foreground(lipgloss.Color("242")) - s.NoItems = lipgloss.NewStyle(). - MarginLeft(2). - Foreground(lipgloss.Color("242")) - s.StatusBar = lipgloss.NewStyle(). Height(1)