diff --git a/cmd/ugitd/args.go b/cmd/ugitd/args.go index df6dd7e22dd051674ff48ec5565172165c3f1347..5dd586ad4dc96aaabe420f74e02da5e4f4b09777 100644 --- a/cmd/ugitd/args.go +++ b/cmd/ugitd/args.go @@ -18,7 +18,6 @@ Meta metaArgs Profile profileArgs Log logArgs ShowPrivate bool - TUI bool } type sshArgs struct { @@ -115,7 +114,6 @@ fs.StringVar(&c.Meta.Title, "meta.title", c.Meta.Title, "App title") fs.StringVar(&c.Meta.Description, "meta.description", c.Meta.Description, "App description") fs.StringVar(&c.Profile.Username, "profile.username", c.Profile.Username, "Username for index page") fs.StringVar(&c.Profile.Email, "profile.email", c.Profile.Email, "Email for index page") - fs.BoolVar(&c.TUI, "tui", c.TUI, "Run the TUI interface directly") fs.Func("profile.links", "Link(s) for index page", func(s string) error { parts := strings.SplitN(s, ",", 2) if len(parts) != 2 { diff --git a/cmd/ugitd/main.go b/cmd/ugitd/main.go index 497c7439e6e2eebf16f2827f78a489beb9f32bdb..afb48cd348805b04c0f4ae04c0d510903740e3d9 100644 --- a/cmd/ugitd/main.go +++ b/cmd/ugitd/main.go @@ -20,7 +20,6 @@ "github.com/go-git/go-git/v5/utils/trace" "go.jolheiser.com/ugit/internal/git" "go.jolheiser.com/ugit/internal/http" "go.jolheiser.com/ugit/internal/ssh" - "go.jolheiser.com/ugit/internal/tui" ) func main() { @@ -39,14 +38,6 @@ } args.RepoDir, err = filepath.Abs(args.RepoDir) if err != nil { panic(err) - } - - // Run TUI mode if requested - if args.TUI { - if err := tui.Run(args.RepoDir); err != nil { - panic(err) - } - return } slog.SetLogLoggerLevel(args.Log.Level) diff --git a/go.mod b/go.mod index 72dc41d21175f92fae6df380bde72dc3b5405d99..1f0c033f3103579b1efe9a683f5e84be74a94d49 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,6 @@ require ( github.com/alecthomas/assert/v2 v2.11.0 github.com/alecthomas/chroma/v2 v2.15.0 - github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.5 - github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c github.com/charmbracelet/wish v1.4.4 github.com/dustin/go-humanize v1.0.1 @@ -31,16 +28,14 @@ github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.4 // indirect github.com/alecthomas/repr v0.4.0 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/bubbletea v1.2.4 // indirect github.com/charmbracelet/keygen v0.5.1 // indirect + github.com/charmbracelet/lipgloss v1.0.0 // indirect github.com/charmbracelet/log v0.4.0 // indirect - github.com/charmbracelet/x/ansi v0.8.0 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/ansi v0.6.0 // indirect github.com/charmbracelet/x/conpty v0.1.0 // indirect github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5 // indirect - github.com/charmbracelet/x/input v0.2.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/charmbracelet/x/termios v0.1.0 // indirect github.com/cloudflare/circl v1.5.0 // indirect @@ -63,20 +58,18 @@ github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mmcloughlin/avo v0.6.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect + github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5 // indirect github.com/pjbgf/sha1cd v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.29.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 4fab0c254ced3c9ceaf1f107c21a445e1a8fba8e..6c331a1240e5f88c7230899d3dc5bc133d3e2ebb 100644 --- a/go.sum +++ b/go.sum @@ -17,40 +17,26 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= -github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= -github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= -github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc= -github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= +github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= github.com/charmbracelet/keygen v0.5.1 h1:zBkkYPtmKDVTw+cwUyY6ZwGDhRxXkEp0Oxs9sqMLqxI= github.com/charmbracelet/keygen v0.5.1/go.mod h1:zznJVmK/GWB6dAtjluqn2qsttiCBhA5MZSiwb80fcHw= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c h1:treQxMBdI2PaD4eOYfFux8stfCkUxhuUxaqGcxKqVpI= github.com/charmbracelet/ssh v0.0.0-20241211182756-4fe22b0f1b7c/go.mod h1:CY1xbl2z+ZeBmNWItKZyxx0zgDgnhmR57+DTsHOobJ4= github.com/charmbracelet/wish v1.4.4 h1:wtfoAMkf8Db9zi+9Lme2f7XKMxL6BqfgDWbqcTUHLaU= github.com/charmbracelet/wish v1.4.4/go.mod h1:XB8v51UxIFMRlUod9lLaAgOsj/wpe+qW9HjsoYIiNMo= -github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= -github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= +github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5 h1:Hx72S6S4jAfrrWE3pv9IbudVdUV4htBgkOX800o17Bk= github.com/charmbracelet/x/errors v0.0.0-20250107110353-48b574af22a5/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= -github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/charmbracelet/x/input v0.2.0 h1:1Sv+y/flcqUfUH2PXNIDKDIdT2G8smOnGOgawqhwy8A= -github.com/charmbracelet/x/input v0.2.0/go.mod h1:KUSFIS6uQymtnr5lHVSOK9j8RvwTD4YHnWnzJUYnd/M= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k= @@ -110,8 +96,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -126,8 +110,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5 h1:NiONcKK0EV5gUZcnCiPMORaZA0eBDc+Fgepl9xl4lZ8= +github.com/muesli/termenv v0.15.3-0.20240509142007-81b8f94111d5/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= @@ -144,8 +128,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -159,8 +141,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= @@ -179,8 +159,8 @@ golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -189,8 +169,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab h1:BMkEEWYOjkvOX7+YKOGbp6jCyQ5pR2j0Ah47p1Vdsx4= +golang.org/x/sys v0.29.1-0.20250107080300-1c14dcadc3ab/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= diff --git a/internal/git/meta.go b/internal/git/meta.go index d84ab834204d7c049e397a131cf107ae9059c4dc..0a13833c1a949c40b3beb1170043183276deed6d 100644 --- a/internal/git/meta.go +++ b/internal/git/meta.go @@ -57,9 +57,6 @@ var s []string if err := json.Unmarshal(b, &s); err != nil { return err } - if *t == nil { - *t = make(TagSet) - } for _, ss := range s { t.Add(ss) } diff --git a/internal/git/repo_utils.go b/internal/git/repo_utils.go deleted file mode 100644 index 003098dc14653a59258057141892b099b3f64030..0000000000000000000000000000000000000000 --- a/internal/git/repo_utils.go +++ /dev/null @@ -1,61 +0,0 @@ -package git - -import ( - "errors" - "io/fs" - "os" - "path/filepath" - "strings" -) - -// ListRepos returns all directory entries in the given directory -func ListRepos(dir string) ([]fs.DirEntry, error) { - entries, err := os.ReadDir(dir) - if err != nil { - if errors.Is(err, fs.ErrNotExist) { - return []fs.DirEntry{}, nil - } - return nil, err - } - return entries, nil -} - -// DeleteRepo deletes a git repository from the filesystem -func DeleteRepo(repoPath string) error { - return os.RemoveAll(repoPath) -} - -// RenameRepo renames a git repository -func RenameRepo(repoDir, oldName, newName string) error { - if !filepath.IsAbs(repoDir) { - return errors.New("repository directory must be an absolute path") - } - - if !filepath.IsAbs(oldName) && !filepath.IsAbs(newName) { - oldPath := filepath.Join(repoDir, oldName) - if !strings.HasSuffix(oldPath, ".git") { - oldPath += ".git" - } - - newPath := filepath.Join(repoDir, newName) - if !strings.HasSuffix(newPath, ".git") { - newPath += ".git" - } - - return os.Rename(oldPath, newPath) - } - - return errors.New("repository names should not be absolute paths") -} - -// RepoPathExists checks if a path exists -func RepoPathExists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { - return true, nil - } - if errors.Is(err, fs.ErrNotExist) { - return false, nil - } - return false, err -} \ No newline at end of file diff --git a/internal/ssh/wish.go b/internal/ssh/wish.go index 2c6c7f626f0aa22f2f4e065df6cbb1da0f005287..bdf504b0e0a2f14a4d7105dd1e9353f57b8fe807 100644 --- a/internal/ssh/wish.go +++ b/internal/ssh/wish.go @@ -12,7 +12,6 @@ "strings" "text/tabwriter" "go.jolheiser.com/ugit/internal/git" - "go.jolheiser.com/ugit/internal/tui" "github.com/charmbracelet/ssh" "github.com/charmbracelet/wish" @@ -100,34 +99,28 @@ return } } - // No args, start TUI + // Repo list if len(cmd) == 0 { - if err := tui.Start(s, repoDir); err != nil { - slog.Error("failed to start TUI", "error", err) - - // Fall back to simple list on TUI error - des, err := os.ReadDir(repoDir) - if err != nil && err != fs.ErrNotExist { - slog.Error("invalid repository", "error", err) + des, err := os.ReadDir(repoDir) + if err != nil && err != fs.ErrNotExist { + slog.Error("invalid repository", "error", err) + } + tw := tabwriter.NewWriter(s, 0, 0, 1, ' ', 0) + for _, de := range des { + if filepath.Ext(de.Name()) != ".git" { + continue } - tw := tabwriter.NewWriter(s, 0, 0, 1, ' ', 0) - for _, de := range des { - if filepath.Ext(de.Name()) != ".git" { - continue - } - repo, err := git.NewRepo(repoDir, de.Name()) - visibility := "❓" - if err == nil { - visibility = "🔓" - if repo.Meta.Private { - visibility = "🔒" - } + repo, err := git.NewRepo(repoDir, de.Name()) + visibility := "❓" + if err == nil { + visibility = "🔓" + if repo.Meta.Private { + visibility = "🔒" } - fmt.Fprintf(tw, "%[1]s\t%[3]s\t%[2]s/%[1]s.git\n", strings.TrimSuffix(de.Name(), ".git"), cloneURL, visibility) } - tw.Flush() + fmt.Fprintf(tw, "%[1]s\t%[3]s\t%[2]s/%[1]s.git\n", strings.TrimSuffix(de.Name(), ".git"), cloneURL, visibility) } - return + tw.Flush() } sh(s) } @@ -181,4 +174,4 @@ // hex length includes 4 byte length prefix and ending newline pktLine := fmt.Sprintf("%04x%s\n", len(msg)+5, msg) _, _ = wish.WriteString(s, pktLine) s.Exit(1) // nolint: errcheck -} \ No newline at end of file +} diff --git a/internal/tui/form.go b/internal/tui/form.go deleted file mode 100644 index db32f2e618c07269890473572ec82f378c4558fe..0000000000000000000000000000000000000000 --- a/internal/tui/form.go +++ /dev/null @@ -1,230 +0,0 @@ -package tui - -import ( - "fmt" - "strings" - - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "go.jolheiser.com/ugit/internal/git" -) - -type repoForm struct { - inputs []textinput.Model - isPrivate bool - focusIndex int - width int - height int - done bool - save bool - selectedRepo *git.Repo -} - -// newRepoForm creates a new repository editing form -func newRepoForm() repoForm { - var inputs []textinput.Model - - nameInput := textinput.New() - nameInput.Placeholder = "Repository name" - nameInput.Focus() - nameInput.Width = 50 - inputs = append(inputs, nameInput) - - descInput := textinput.New() - descInput.Placeholder = "Repository description" - descInput.Width = 50 - inputs = append(inputs, descInput) - - tagsInput := textinput.New() - tagsInput.Placeholder = "Tags (comma separated)" - tagsInput.Width = 50 - inputs = append(inputs, tagsInput) - - return repoForm{ - inputs: inputs, - focusIndex: 0, - } -} - -// setValues sets the form values from the selected repo -func (f *repoForm) setValues(repo *git.Repo) { - f.inputs[0].SetValue(repo.Name()) - f.inputs[1].SetValue(repo.Meta.Description) - f.inputs[2].SetValue(strings.Join(repo.Meta.Tags.Slice(), ", ")) - f.isPrivate = repo.Meta.Private - - f.inputs[0].Focus() - f.focusIndex = 0 -} - -// setSize sets the form dimensions -func (f *repoForm) setSize(width, height int) { - f.width = width - f.height = height - - for i := range f.inputs { - f.inputs[i].Width = width - 10 - } -} - -// isPrivateToggleFocused returns true if the private toggle is focused -func (f *repoForm) isPrivateToggleFocused() bool { - return f.focusIndex == len(f.inputs) -} - -// isSaveButtonFocused returns true if the save button is focused -func (f *repoForm) isSaveButtonFocused() bool { - return f.focusIndex == len(f.inputs)+1 -} - -// isCancelButtonFocused returns true if the cancel button is focused -func (f *repoForm) isCancelButtonFocused() bool { - return f.focusIndex == len(f.inputs)+2 -} - -// Update handles form updates -func (f repoForm) Update(msg tea.Msg) (repoForm, tea.Cmd) { - var cmds []tea.Cmd - - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "tab", "shift+tab", "up", "down": - if msg.String() == "up" || msg.String() == "shift+tab" { - f.focusIndex-- - if f.focusIndex < 0 { - f.focusIndex = len(f.inputs) + 3 - 1 - } - } else { - f.focusIndex++ - if f.focusIndex >= len(f.inputs)+3 { - f.focusIndex = 0 - } - } - - for i := range f.inputs { - if i == f.focusIndex { - cmds = append(cmds, f.inputs[i].Focus()) - } else { - f.inputs[i].Blur() - } - } - - case "enter": - if f.isSaveButtonFocused() { - f.done = true - f.save = true - return f, nil - } - - if f.isCancelButtonFocused() { - f.done = true - f.save = false - return f, nil - } - - case "esc": - f.done = true - f.save = false - return f, nil - - case " ": - if f.isPrivateToggleFocused() { - f.isPrivate = !f.isPrivate - } - - if f.isSaveButtonFocused() { - f.done = true - f.save = true - return f, nil - } - - if f.isCancelButtonFocused() { - f.done = true - f.save = false - return f, nil - } - } - } - - for i := range f.inputs { - if i == f.focusIndex { - var cmd tea.Cmd - f.inputs[i], cmd = f.inputs[i].Update(msg) - cmds = append(cmds, cmd) - } - } - - return f, tea.Batch(cmds...) -} - -// View renders the form -func (f repoForm) View() string { - var b strings.Builder - - formStyle := lipgloss.NewStyle(). - BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("170")). - Padding(1, 2) - - titleStyle := lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color("170")). - MarginBottom(1) - - b.WriteString(titleStyle.Render("Edit Repository")) - b.WriteString("\n\n") - - b.WriteString("Repository Name:\n") - b.WriteString(f.inputs[0].View()) - b.WriteString("\n\n") - - b.WriteString("Description:\n") - b.WriteString(f.inputs[1].View()) - b.WriteString("\n\n") - - b.WriteString("Tags (comma separated):\n") - b.WriteString(f.inputs[2].View()) - b.WriteString("\n\n") - - toggleStyle := lipgloss.NewStyle() - if f.isPrivateToggleFocused() { - toggleStyle = toggleStyle.Foreground(lipgloss.Color("170")).Bold(true) - } - - visibility := "Public 🔓" - if f.isPrivate { - visibility = "Private 🔒" - } - - b.WriteString(toggleStyle.Render(fmt.Sprintf("[%s] %s", visibility, "Toggle with Space"))) - b.WriteString("\n\n") - - buttonStyle := lipgloss.NewStyle(). - Padding(0, 3). - MarginRight(1) - - focusedButtonStyle := buttonStyle.Copy(). - Foreground(lipgloss.Color("0")). - Background(lipgloss.Color("170")). - Bold(true) - - saveButton := buttonStyle.Render("[ Save ]") - cancelButton := buttonStyle.Render("[ Cancel ]") - - if f.isSaveButtonFocused() { - saveButton = focusedButtonStyle.Render("[ Save ]") - } - - if f.isCancelButtonFocused() { - cancelButton = focusedButtonStyle.Render("[ Cancel ]") - } - - b.WriteString(saveButton + cancelButton) - b.WriteString("\n\n") - - b.WriteString("\nTab: Next • Shift+Tab: Previous • Enter: Select • Esc: Cancel") - - return formStyle.Width(f.width - 4).Render(b.String()) -} diff --git a/internal/tui/keymap.go b/internal/tui/keymap.go deleted file mode 100644 index 4357d520cbd158967793457dc910b4fdbc650c81..0000000000000000000000000000000000000000 --- a/internal/tui/keymap.go +++ /dev/null @@ -1,65 +0,0 @@ -package tui - -import ( - "github.com/charmbracelet/bubbles/key" -) - -// keyMap defines the keybindings for the TUI -type keyMap struct { - Up key.Binding - Down key.Binding - Edit key.Binding - Delete key.Binding - Help key.Binding - Quit key.Binding - Confirm key.Binding - Cancel key.Binding -} - -// ShortHelp returns keybindings to be shown in the mini help view. -func (k keyMap) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Edit, k.Delete, k.Quit} -} - -// FullHelp returns keybindings for the expanded help view. -func (k keyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{ - {k.Up, k.Down, k.Edit}, - {k.Delete, k.Help, k.Quit}, - } -} - -var keys = keyMap{ - Up: key.NewBinding( - key.WithKeys("up", "k"), - key.WithHelp("↑/k", "up"), - ), - Down: key.NewBinding( - key.WithKeys("down", "j"), - key.WithHelp("↓/j", "down"), - ), - Edit: key.NewBinding( - key.WithKeys("e"), - key.WithHelp("e", "edit"), - ), - Delete: key.NewBinding( - key.WithKeys("d"), - key.WithHelp("d", "delete"), - ), - Help: key.NewBinding( - key.WithKeys("?"), - key.WithHelp("?", "help"), - ), - Quit: key.NewBinding( - key.WithKeys("q", "ctrl+c"), - key.WithHelp("q", "quit"), - ), - Confirm: key.NewBinding( - key.WithKeys("y"), - key.WithHelp("y", "confirm"), - ), - Cancel: key.NewBinding( - key.WithKeys("n", "esc"), - key.WithHelp("n", "cancel"), - ), -} \ No newline at end of file diff --git a/internal/tui/main.go b/internal/tui/main.go deleted file mode 100644 index 7209857945753f06b1166e4df36506389087f54e..0000000000000000000000000000000000000000 --- a/internal/tui/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package tui - -import ( - "fmt" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -// Run runs the TUI standalone, useful for development or local usage -func Run(repoDir string) error { - model := Model{ - repoDir: repoDir, - help: help.New(), - keys: keys, - activeView: ViewList, - repoForm: newRepoForm(), - } - - repos, err := loadRepos(repoDir) - if err != nil { - return fmt.Errorf("failed to load repos: %w", err) - } - model.repos = repos - - items := make([]list.Item, len(repos)) - for i, repo := range repos { - items[i] = repoItem{repo: repo} - } - - delegate := list.NewDefaultDelegate() - delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.Foreground(lipgloss.Color("170")) - delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.Foreground(lipgloss.Color("244")) - - repoList := list.New(items, delegate, 0, 0) - repoList.Title = "Git Repositories" - repoList.SetShowStatusBar(true) - repoList.SetFilteringEnabled(true) - repoList.Styles.Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("170")).Padding(0, 0, 0, 2) - repoList.StatusMessageLifetime = 3 - - model.repoList = repoList - - p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithMouseCellMotion()) - - _, err = p.Run() - return err -} diff --git a/internal/tui/repo_item.go b/internal/tui/repo_item.go deleted file mode 100644 index 672d8ab27d5bf376438fbdc5f99491850ec238db..0000000000000000000000000000000000000000 --- a/internal/tui/repo_item.go +++ /dev/null @@ -1,72 +0,0 @@ -package tui - -import ( - "strings" - - "go.jolheiser.com/ugit/internal/git" -) - -// repoItem represents a repository item in the list -type repoItem struct { - repo *git.Repo -} - -// Title returns the title for the list item -func (r repoItem) Title() string { - return r.repo.Name() -} - -// Description returns the description for the list item -func (r repoItem) Description() string { - var builder strings.Builder - - if r.repo.Meta.Private { - builder.WriteString("🔒") - } else { - builder.WriteString("🔓") - } - - builder.WriteString(" • ") - - if r.repo.Meta.Description != "" { - builder.WriteString(r.repo.Meta.Description) - } else { - builder.WriteString("No description") - } - - builder.WriteString(" • ") - - builder.WriteString("[") - if len(r.repo.Meta.Tags) > 0 { - builder.WriteString(strings.Join(r.repo.Meta.Tags.Slice(), ", ")) - } - builder.WriteString("]") - - builder.WriteString(" • ") - - lastCommit, err := r.repo.LastCommit() - if err == nil { - builder.WriteString(lastCommit.Short()) - } else { - builder.WriteString("deadbeef") - } - - return builder.String() -} - -// FilterValue returns the value to use for filtering -func (r repoItem) FilterValue() string { - var builder strings.Builder - builder.WriteString(r.repo.Name()) - builder.WriteString(" ") - builder.WriteString(r.repo.Meta.Description) - - if len(r.repo.Meta.Tags) > 0 { - for _, tag := range r.repo.Meta.Tags.Slice() { - builder.WriteString(" ") - builder.WriteString(tag) - } - } - - return strings.ToLower(builder.String()) -} diff --git a/internal/tui/tui.go b/internal/tui/tui.go deleted file mode 100644 index 607b75ad416662e4c4bc2e3b49a47bf521471354..0000000000000000000000000000000000000000 --- a/internal/tui/tui.go +++ /dev/null @@ -1,321 +0,0 @@ -package tui - -import ( - "fmt" - "log/slog" - "path/filepath" - "strings" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/ssh" - "go.jolheiser.com/ugit/internal/git" -) - -// Model is the main TUI model -type Model struct { - repoList list.Model - repos []*git.Repo - repoDir string - width int - height int - help help.Model - keys keyMap - activeView View - repoForm repoForm - session ssh.Session -} - -// View represents the current active view in the TUI -type View int - -const ( - ViewList View = iota - ViewForm - ViewConfirmDelete -) - -// New creates a new TUI model -func New(s ssh.Session, repoDir string) (*Model, error) { - repos, err := loadRepos(repoDir) - if err != nil { - return nil, fmt.Errorf("failed to load repos: %w", err) - } - - items := make([]list.Item, len(repos)) - for i, repo := range repos { - items[i] = repoItem{repo: repo} - } - - delegate := list.NewDefaultDelegate() - delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.Foreground(lipgloss.Color("170")) - delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.Foreground(lipgloss.Color("244")) - - repoList := list.New(items, delegate, 0, 0) - repoList.Title = "Git Repositories" - repoList.SetShowStatusBar(true) - repoList.SetFilteringEnabled(true) - repoList.Styles.Title = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("170")).Padding(0, 0, 0, 2) - repoList.StatusMessageLifetime = 3 - - repoList.FilterInput.Placeholder = "Type to filter repositories..." - repoList.FilterInput.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) - repoList.FilterInput.TextStyle = lipgloss.NewStyle() - - help := help.New() - - repoForm := newRepoForm() - - return &Model{ - repoList: repoList, - repos: repos, - repoDir: repoDir, - help: help, - keys: keys, - activeView: ViewList, - repoForm: repoForm, - session: s, - }, nil -} - -// loadRepos loads all git repositories from the given directory -func loadRepos(repoDir string) ([]*git.Repo, error) { - entries, err := git.ListRepos(repoDir) - if err != nil { - return nil, err - } - - repos := make([]*git.Repo, 0, len(entries)) - for _, entry := range entries { - if !strings.HasSuffix(entry.Name(), ".git") { - continue - } - repo, err := git.NewRepo(repoDir, entry.Name()) - if err != nil { - slog.Error("error loading repo", "name", entry.Name(), "error", err) - continue - } - repos = append(repos, repo) - } - - return repos, nil -} - -// Init initializes the model -func (m Model) Init() tea.Cmd { - return nil -} - -// Update handles all the messages and updates the model accordingly -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, m.keys.Quit): - return m, tea.Quit - case key.Matches(msg, m.keys.Help): - m.help.ShowAll = !m.help.ShowAll - } - - switch m.activeView { - case ViewList: - var cmd tea.Cmd - m.repoList, cmd = m.repoList.Update(msg) - cmds = append(cmds, cmd) - - if m.repoList.FilterState() == list.Filtering { - break - } - - switch { - case key.Matches(msg, m.keys.Edit): - if len(m.repos) == 0 { - m.repoList.NewStatusMessage("No repositories to edit") - break - } - - selectedItem := m.repoList.SelectedItem().(repoItem) - m.repoForm.selectedRepo = selectedItem.repo - - m.repoForm.setValues(selectedItem.repo) - m.activeView = ViewForm - return m, textinput.Blink - - case key.Matches(msg, m.keys.Delete): - if len(m.repos) == 0 { - m.repoList.NewStatusMessage("No repositories to delete") - break - } - - m.activeView = ViewConfirmDelete - } - - case ViewForm: - var cmd tea.Cmd - m.repoForm, cmd = m.repoForm.Update(msg) - cmds = append(cmds, cmd) - - if m.repoForm.done { - if m.repoForm.save { - selectedRepo := m.repoForm.selectedRepo - repoDir := filepath.Dir(selectedRepo.Path()) - oldName := selectedRepo.Name() - newName := m.repoForm.inputs[0].Value() - - var renamed bool - if oldName != newName { - if err := git.RenameRepo(repoDir, oldName, newName); err != nil { - m.repoList.NewStatusMessage(fmt.Sprintf("Error renaming repo: %s", err)) - } else { - m.repoList.NewStatusMessage(fmt.Sprintf("Repository renamed from %s to %s", oldName, newName)) - renamed = true - } - } - - if renamed { - if newRepo, err := git.NewRepo(repoDir, newName+".git"); err == nil { - selectedRepo = newRepo - } else { - m.repoList.NewStatusMessage(fmt.Sprintf("Error loading renamed repo: %s", err)) - } - } - - selectedRepo.Meta.Description = m.repoForm.inputs[1].Value() - selectedRepo.Meta.Private = m.repoForm.isPrivate - - tags := make(git.TagSet) - for _, tag := range strings.Split(m.repoForm.inputs[2].Value(), ",") { - tag = strings.TrimSpace(tag) - if tag != "" { - tags.Add(tag) - } - } - selectedRepo.Meta.Tags = tags - - if err := selectedRepo.SaveMeta(); err != nil { - m.repoList.NewStatusMessage(fmt.Sprintf("Error saving repo metadata: %s", err)) - } else if !renamed { - m.repoList.NewStatusMessage("Repository updated successfully") - } - } - - m.repoForm.done = false - m.repoForm.save = false - m.activeView = ViewList - - if repos, err := loadRepos(m.repoDir); err == nil { - m.repos = repos - items := make([]list.Item, len(repos)) - for i, repo := range repos { - items[i] = repoItem{repo: repo} - } - m.repoList.SetItems(items) - } - } - - case ViewConfirmDelete: - switch { - case key.Matches(msg, m.keys.Confirm): - selectedItem := m.repoList.SelectedItem().(repoItem) - repo := selectedItem.repo - - if err := git.DeleteRepo(repo.Path()); err != nil { - m.repoList.NewStatusMessage(fmt.Sprintf("Error deleting repo: %s", err)) - } else { - m.repoList.NewStatusMessage(fmt.Sprintf("Repository %s deleted", repo.Name())) - - if repos, err := loadRepos(m.repoDir); err == nil { - m.repos = repos - items := make([]list.Item, len(repos)) - for i, repo := range repos { - items[i] = repoItem{repo: repo} - } - m.repoList.SetItems(items) - } - } - m.activeView = ViewList - - case key.Matches(msg, m.keys.Cancel): - m.activeView = ViewList - } - } - - case tea.WindowSizeMsg: - m.width = msg.Width - m.height = msg.Height - - headerHeight := 3 - footerHeight := 2 - - m.repoList.SetSize(msg.Width, msg.Height-headerHeight-footerHeight) - m.repoForm.setSize(msg.Width, msg.Height) - - m.help.Width = msg.Width - } - - return m, tea.Batch(cmds...) -} - -// View renders the current UI -func (m Model) View() string { - switch m.activeView { - case ViewList: - return fmt.Sprintf("%s\n%s", m.repoList.View(), m.help.View(m.keys)) - - case ViewForm: - return m.repoForm.View() - - case ViewConfirmDelete: - selectedItem := m.repoList.SelectedItem().(repoItem) - repo := selectedItem.repo - - confirmStyle := lipgloss.NewStyle(). - BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("170")). - Padding(1, 2). - Width(m.width - 4). - Align(lipgloss.Center) - - confirmText := fmt.Sprintf( - "Are you sure you want to delete repository '%s'?\n\nThis action cannot be undone!\n\nPress y to confirm or n to cancel.", - repo.Name(), - ) - - return confirmStyle.Render(confirmText) - } - - return "" -} - -// Start runs the TUI -func Start(s ssh.Session, repoDir string) error { - model, err := New(s, repoDir) - if err != nil { - return err - } - - // Get terminal dimensions from SSH session if available - pty, _, isPty := s.Pty() - if isPty && pty.Window.Width > 0 && pty.Window.Height > 0 { - // Initialize with correct size - model.width = pty.Window.Width - model.height = pty.Window.Height - - headerHeight := 3 - footerHeight := 2 - model.repoList.SetSize(pty.Window.Width, pty.Window.Height-headerHeight-footerHeight) - model.repoForm.setSize(pty.Window.Width, pty.Window.Height) - model.help.Width = pty.Window.Width - } - - p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithMouseCellMotion(), tea.WithInput(s), tea.WithOutput(s)) - - _, err = p.Run() - return err -}