refactor(tui): move to Bubble Tea, Lip Gloss and Bubbles v2

Switch imports to the charm.land v2 modules. Return tea.View from Model.View,
set alt screen and window title there, use KeyPressMsg and the "space" key
name, and call SetWidth on text inputs.
This commit is contained in:
2026-10-04 10:12:33 +01:00
parent 750e43501c
commit 22f8f7a008
10 changed files with 101 additions and 83 deletions
+7 -11
View File
@@ -3,9 +3,9 @@ module github.com/lukaszraczylo/lolcathost
go 1.26.0 go 1.26.0
require ( require (
github.com/charmbracelet/bubbles v1.0.0 charm.land/bubbles/v2 v2.2.1
github.com/charmbracelet/bubbletea v1.3.10 charm.land/bubbletea/v2 v2.0.10
github.com/charmbracelet/lipgloss v1.1.0 charm.land/lipgloss/v2 v2.0.6
github.com/fsnotify/fsnotify v1.10.1 github.com/fsnotify/fsnotify v1.10.1
github.com/lukaszraczylo/oss-telemetry v0.2.5 github.com/lukaszraczylo/oss-telemetry v0.2.5
github.com/stretchr/testify v1.12.1 github.com/stretchr/testify v1.12.1
@@ -15,23 +15,19 @@ require (
require ( require (
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect
github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/ansi v0.11.8 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.30 // indirect github.com/mattn/go-runewidth v0.0.30 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v1.2.0 // indirect github.com/xo/terminfo v1.2.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/text v0.42.0 // indirect golang.org/x/sync v0.23.0 // indirect
) )
+18 -27
View File
@@ -1,49 +1,41 @@
charm.land/bubbles/v2 v2.2.1 h1:Fq1+qm5hV6GkvzLQDhCBpXXE5tLgvh1PRriCLwSvIQU=
charm.land/bubbles/v2 v2.2.1/go.mod h1:wdMgn+sje1KNXdwFizIWjbf328fIUBxqEmJ/vYPo8yc=
charm.land/bubbletea/v2 v2.0.10 h1:oolvo20VBpI0PfqE7iFjkZ1bx0WpmXGfnKz5Yldjq5o=
charm.land/bubbletea/v2 v2.0.10/go.mod h1:QOatcnhOjYIfxzUSTz6raF7Ex4R/rIuHa3SnBdCCpMc=
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 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-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ= github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0= github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
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/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss= github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lukaszraczylo/oss-telemetry v0.2.5 h1:W6Qgwp7IY4yBRO361MmnEAmM59motdQpbAFAx8v6fJM= github.com/lukaszraczylo/oss-telemetry v0.2.5 h1:W6Qgwp7IY4yBRO361MmnEAmM59motdQpbAFAx8v6fJM=
github.com/lukaszraczylo/oss-telemetry v0.2.5/go.mod h1:+Cn78qZo8rc3T9eZt0v3oICYRdd75wORtSidc8lNjDQ= github.com/lukaszraczylo/oss-telemetry v0.2.5/go.mod h1:+Cn78qZo8rc3T9eZt0v3oICYRdd75wORtSidc8lNjDQ=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk= github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk=
github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/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 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= 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/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
@@ -52,11 +44,10 @@ github.com/xo/terminfo v1.2.0 h1:d0ZTOCpuGE0lwSAOs0zcJjwz3jWQyqcRt9XbGJpCOl4=
github.com/xo/terminfo v1.2.0/go.mod h1:lGzkSo8Fe7IRh/w+Gqz7n5mDog4FVXCj3gy/DYTBqio= github.com/xo/terminfo v1.2.0/go.mod h1:lGzkSo8Fe7IRh/w+Gqz7n5mDog4FVXCj3gy/DYTBqio=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+35 -29
View File
@@ -7,9 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/charmbracelet/bubbles/textinput" "charm.land/bubbles/v2/textinput"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"github.com/lukaszraczylo/lolcathost/internal/client" "github.com/lukaszraczylo/lolcathost/internal/client"
"github.com/lukaszraczylo/lolcathost/internal/config" "github.com/lukaszraczylo/lolcathost/internal/config"
@@ -143,7 +143,7 @@ func NewModel(socketPath string) *Model {
searchInput := textinput.New() searchInput := textinput.New()
searchInput.Placeholder = "Search..." searchInput.Placeholder = "Search..."
searchInput.CharLimit = 100 searchInput.CharLimit = 100
searchInput.Width = 50 searchInput.SetWidth(50)
return &Model{ return &Model{
client: client.New(socketPath), client: client.New(socketPath),
@@ -161,7 +161,6 @@ func NewModel(socketPath string) *Model {
func (m *Model) Init() tea.Cmd { func (m *Model) Init() tea.Cmd {
return tea.Batch( return tea.Batch(
m.connect(), m.connect(),
tea.SetWindowTitle("lolcathost"),
m.tick(), m.tick(),
m.checkForUpdate(), m.checkForUpdate(),
) )
@@ -330,9 +329,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if searchWidth > 60 { if searchWidth > 60 {
searchWidth = 60 searchWidth = 60
} }
m.searchInput.Width = searchWidth m.searchInput.SetWidth(searchWidth)
case tea.KeyMsg: case tea.KeyPressMsg:
cmd := m.handleKey(msg) cmd := m.handleKey(msg)
if cmd != nil { if cmd != nil {
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
@@ -505,7 +504,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd {
// Global keys // Global keys
switch msg.String() { switch msg.String() {
case "ctrl+c": case "ctrl+c":
@@ -535,7 +534,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleListKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "q": case "q":
return tea.Quit return tea.Quit
@@ -549,7 +548,7 @@ func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
m.list.MoveUp() m.list.MoveUp()
case "down", "j": case "down", "j":
m.list.MoveDown() m.list.MoveDown()
case " ", "enter": case "space", "enter":
return m.toggleSelected() return m.toggleSelected()
case "n": case "n":
m.mode = ViewForm m.mode = ViewForm
@@ -587,7 +586,7 @@ func (m *Model) handleListKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleFormKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleFormKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc": case "esc":
m.mode = ViewList m.mode = ViewList
@@ -615,7 +614,7 @@ func (m *Model) handleFormKey(msg tea.KeyMsg) tea.Cmd {
return m.form.Update(msg) return m.form.Update(msg)
} }
func (m *Model) handlePresetKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handlePresetKey(msg tea.KeyPressMsg) tea.Cmd {
// Handle based on preset picker mode // Handle based on preset picker mode
switch m.presetPicker.Mode() { switch m.presetPicker.Mode() {
case PresetModeSelect: case PresetModeSelect:
@@ -630,7 +629,7 @@ func (m *Model) handlePresetKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handlePresetSelectKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handlePresetSelectKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc", "q": case "esc", "q":
m.mode = ViewList m.mode = ViewList
@@ -652,7 +651,7 @@ func (m *Model) handlePresetSelectKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handlePresetFormKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handlePresetFormKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc": case "esc":
m.presetPicker.CancelForm() m.presetPicker.CancelForm()
@@ -690,7 +689,7 @@ func (m *Model) handlePresetFormKey(msg tea.KeyMsg) tea.Cmd {
return m.presetPicker.Update(msg) return m.presetPicker.Update(msg)
} }
func (m *Model) handlePresetPickerKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handlePresetPickerKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc": case "esc":
m.presetPicker.ClosePicker() m.presetPicker.ClosePicker()
@@ -700,13 +699,13 @@ func (m *Model) handlePresetPickerKey(msg tea.KeyMsg) tea.Cmd {
m.presetPicker.PickerMoveUp() m.presetPicker.PickerMoveUp()
case "down", "j": case "down", "j":
m.presetPicker.PickerMoveDown() m.presetPicker.PickerMoveDown()
case " ": case "space":
m.presetPicker.TogglePickerSelection() m.presetPicker.TogglePickerSelection()
} }
return nil return nil
} }
func (m *Model) handlePresetDeleteKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handlePresetDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "y", "Y": case "y", "Y":
if preset := m.presetPicker.Selected(); preset != "" { if preset := m.presetPicker.Selected(); preset != "" {
@@ -719,7 +718,7 @@ func (m *Model) handlePresetDeleteKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleGroupKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleGroupKey(msg tea.KeyPressMsg) tea.Cmd {
// Handle based on group picker mode // Handle based on group picker mode
switch m.groupPicker.Mode() { switch m.groupPicker.Mode() {
case GroupModeSelect: case GroupModeSelect:
@@ -732,7 +731,7 @@ func (m *Model) handleGroupKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleGroupSelectKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleGroupSelectKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc", "q": case "esc", "q":
m.mode = ViewList m.mode = ViewList
@@ -750,7 +749,7 @@ func (m *Model) handleGroupSelectKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleGroupFormKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleGroupFormKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc": case "esc":
m.groupPicker.CancelForm() m.groupPicker.CancelForm()
@@ -770,7 +769,7 @@ func (m *Model) handleGroupFormKey(msg tea.KeyMsg) tea.Cmd {
return m.groupPicker.Update(msg) return m.groupPicker.Update(msg)
} }
func (m *Model) handleGroupDeleteKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleGroupDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "y", "Y": case "y", "Y":
if group := m.groupPicker.Selected(); group != "" { if group := m.groupPicker.Selected(); group != "" {
@@ -783,7 +782,7 @@ func (m *Model) handleGroupDeleteKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleBackupKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleBackupKey(msg tea.KeyPressMsg) tea.Cmd {
switch m.backupPicker.Mode() { switch m.backupPicker.Mode() {
case BackupModeSelect: case BackupModeSelect:
return m.handleBackupSelectKey(msg) return m.handleBackupSelectKey(msg)
@@ -793,7 +792,7 @@ func (m *Model) handleBackupKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleBackupSelectKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleBackupSelectKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc", "q": case "esc", "q":
m.mode = ViewList m.mode = ViewList
@@ -821,7 +820,7 @@ func (m *Model) handleBackupSelectKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleBackupRestoreKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleBackupRestoreKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "y", "Y": case "y", "Y":
if backup := m.backupPicker.Selected(); backup != "" { if backup := m.backupPicker.Selected(); backup != "" {
@@ -834,7 +833,7 @@ func (m *Model) handleBackupRestoreKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleHelpKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleHelpKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc", "q", "?": case "esc", "q", "?":
m.mode = ViewList m.mode = ViewList
@@ -842,7 +841,7 @@ func (m *Model) handleHelpKey(msg tea.KeyMsg) tea.Cmd {
return nil return nil
} }
func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleSearchKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "esc": case "esc":
m.mode = ViewList m.mode = ViewList
@@ -860,7 +859,7 @@ func (m *Model) handleSearchKey(msg tea.KeyMsg) tea.Cmd {
return cmd return cmd
} }
func (m *Model) handleConfirmDeleteKey(msg tea.KeyMsg) tea.Cmd { func (m *Model) handleConfirmDeleteKey(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "y", "Y": case "y", "Y":
alias := m.pendingDeleteAlias alias := m.pendingDeleteAlias
@@ -900,7 +899,14 @@ func (m *Model) setSuccess(msg string) {
} }
// View renders the UI. // View renders the UI.
func (m *Model) View() string { func (m *Model) View() tea.View {
v := tea.NewView(m.render())
v.AltScreen = true
v.WindowTitle = "lolcathost"
return v
}
func (m *Model) render() string {
var sb strings.Builder var sb strings.Builder
// Title with version // Title with version
@@ -1142,7 +1148,7 @@ func RunWithVersion(socketPath, version, githubOwner, githubRepo string) error {
m.version = version m.version = version
m.githubOwner = githubOwner m.githubOwner = githubOwner
m.githubRepo = githubRepo m.githubRepo = githubRepo
p := tea.NewProgram(m, tea.WithAltScreen()) p := tea.NewProgram(m)
_, err := p.Run() _, err := p.Run()
return err return err
+25
View File
@@ -0,0 +1,25 @@
package tui
import (
"testing"
tea "charm.land/bubbletea/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestModel_View_UsesAltScreenAndTitle(t *testing.T) {
m := NewModel("/nonexistent.sock")
v := m.View()
assert.True(t, v.AltScreen)
assert.Equal(t, "lolcathost", v.WindowTitle)
assert.Contains(t, v.Content, "lolcathost - Host Management")
}
func TestModel_SpaceKeyIsNamedSpace(t *testing.T) {
msg := tea.KeyPressMsg{Code: tea.KeySpace, Text: " "}
require.Equal(t, "space", msg.String())
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"github.com/lukaszraczylo/lolcathost/internal/protocol" "github.com/lukaszraczylo/lolcathost/internal/protocol"
) )
+4 -4
View File
@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/charmbracelet/bubbles/textinput" "charm.land/bubbles/v2/textinput"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/lukaszraczylo/lolcathost/internal/config" "github.com/lukaszraczylo/lolcathost/internal/config"
) )
@@ -127,14 +127,14 @@ func (f *Form) SetSize(width, height int) {
inputWidth := min(50, width-10) inputWidth := min(50, width-10)
for i := range f.fields { for i := range f.fields {
f.fields[i].Width = inputWidth f.fields[i].SetWidth(inputWidth)
} }
} }
// Update handles input events. // Update handles input events.
func (f *Form) Update(msg tea.Msg) tea.Cmd { func (f *Form) Update(msg tea.Msg) tea.Cmd {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyPressMsg:
// Handle group dropdown navigation // Handle group dropdown navigation
if f.focus == FieldGroup { if f.focus == FieldGroup {
switch msg.String() { switch msg.String() {
+4 -4
View File
@@ -4,8 +4,8 @@ package tui
import ( import (
"strings" "strings"
"github.com/charmbracelet/bubbles/textinput" "charm.land/bubbles/v2/textinput"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
) )
// GroupMode represents the group view mode. // GroupMode represents the group view mode.
@@ -53,7 +53,7 @@ func (g *GroupPicker) SetGroups(groups []string) {
func (g *GroupPicker) SetSize(width, height int) { func (g *GroupPicker) SetSize(width, height int) {
g.width = width g.width = width
g.height = height g.height = height
g.input.Width = min(50, width-10) g.input.SetWidth(min(50, width-10))
} }
// MoveUp moves the cursor up. // MoveUp moves the cursor up.
@@ -126,7 +126,7 @@ func (g *GroupPicker) CancelForm() {
} }
// Update handles input events for form mode. // Update handles input events for form mode.
func (g *GroupPicker) Update(msg tea.KeyMsg) tea.Cmd { func (g *GroupPicker) Update(msg tea.KeyPressMsg) tea.Cmd {
var cmd tea.Cmd var cmd tea.Cmd
g.input, cmd = g.input.Update(msg) g.input, cmd = g.input.Update(msg)
return cmd return cmd
+2 -2
View File
@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"github.com/charmbracelet/lipgloss/table" "charm.land/lipgloss/v2/table"
"github.com/lukaszraczylo/lolcathost/internal/protocol" "github.com/lukaszraczylo/lolcathost/internal/protocol"
) )
+4 -4
View File
@@ -4,8 +4,8 @@ package tui
import ( import (
"strings" "strings"
"github.com/charmbracelet/bubbles/textinput" "charm.land/bubbles/v2/textinput"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/lukaszraczylo/lolcathost/internal/protocol" "github.com/lukaszraczylo/lolcathost/internal/protocol"
) )
@@ -108,7 +108,7 @@ func (p *PresetPicker) SetSize(width, height int) {
inputWidth := min(60, width-10) inputWidth := min(60, width-10)
for i := range p.fields { for i := range p.fields {
p.fields[i].Width = inputWidth p.fields[i].SetWidth(inputWidth)
} }
} }
@@ -223,7 +223,7 @@ func (p *PresetPicker) CancelForm() {
} }
// Update handles input events for form mode. // Update handles input events for form mode.
func (p *PresetPicker) Update(msg tea.KeyMsg) tea.Cmd { func (p *PresetPicker) Update(msg tea.KeyPressMsg) tea.Cmd {
switch msg.String() { switch msg.String() {
case "tab", "down": case "tab", "down":
p.nextField() p.nextField()
+1 -1
View File
@@ -4,7 +4,7 @@ package tui
import ( import (
"strings" "strings"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
// Colors - matching kportal style, optimized for dark terminals // Colors - matching kportal style, optimized for dark terminals