diff options
28 files changed, 242 insertions, 249 deletions
@@ -3,35 +3,29 @@ module ior go 1.26.0 require ( + charm.land/bubbles/v2 v2.0.0 + charm.land/bubbletea/v2 v2.0.1 + charm.land/lipgloss/v2 v2.0.0 github.com/DataDog/zstd v1.5.7 github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab - github.com/charmbracelet/bubbles v1.0.0 - github.com/charmbracelet/bubbletea v1.3.10 - github.com/charmbracelet/lipgloss v1.1.0 + github.com/charmbracelet/x/term v0.2.2 github.com/magefile/mage v1.15.0 ) require ( github.com/atotto/clipboard v0.1.4 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect - github.com/charmbracelet/x/cellbuf v0.0.15 // indirect - github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/clipperhouse/displaywidth v0.9.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.5.0 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // 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/uax29/v2 v2.7.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/mattn/go-runewidth v0.0.20 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect ) @@ -1,53 +1,45 @@ +charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s= +charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI= +charm.land/bubbletea/v2 v2.0.1 h1:B8e9zzK7x9JJ+XvHGF4xnYu9Xa0E0y0MyggY6dbaCfQ= +charm.land/bubbletea/v2 v2.0.1/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= +charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo= +charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14= github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab h1:w74AraWsnj+AgEOk2uERlLtECCWutMtuwCGCCWzpBBs= github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk= 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/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.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= -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/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM= +github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= -github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= -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 h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= +github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= 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/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= -github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +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/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -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.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= -github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -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/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= 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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -58,11 +50,9 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/tui/common/keys.go b/internal/tui/common/keys.go index ba17998..31fdf64 100644 --- a/internal/tui/common/keys.go +++ b/internal/tui/common/keys.go @@ -1,6 +1,6 @@ package common -import "github.com/charmbracelet/bubbles/key" +import "charm.land/bubbles/v2/key" // HelpSection groups related key bindings under a shared heading. type HelpSection struct { diff --git a/internal/tui/common/styles.go b/internal/tui/common/styles.go index d4c75ff..06ed596 100644 --- a/internal/tui/common/styles.go +++ b/internal/tui/common/styles.go @@ -1,6 +1,6 @@ package common -import "github.com/charmbracelet/lipgloss" +import "charm.land/lipgloss/v2" var ( // Palette colors shared across the TUI package. diff --git a/internal/tui/dashboard/files.go b/internal/tui/dashboard/files.go index 80e3037..0393553 100644 --- a/internal/tui/dashboard/files.go +++ b/internal/tui/dashboard/files.go @@ -7,7 +7,7 @@ import ( "sort" "strconv" - "github.com/charmbracelet/bubbles/table" + "charm.land/bubbles/v2/table" ) type DirSnapshot struct { diff --git a/internal/tui/dashboard/histogram_test.go b/internal/tui/dashboard/histogram_test.go index 7790394..48297a2 100644 --- a/internal/tui/dashboard/histogram_test.go +++ b/internal/tui/dashboard/histogram_test.go @@ -6,7 +6,7 @@ import ( "ior/internal/statsengine" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func TestRenderHistogramNoBuckets(t *testing.T) { diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index fc9caf6..39150e8 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" + "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" ) const defaultRefreshMs = 1000 @@ -288,7 +288,7 @@ func (m *Model) SetPidFilter(pid int) { } // View renders the tab bar, active tab scaffold, and help bar. -func (m Model) View() string { +func (m Model) View() tea.View { width, height := common.EffectiveViewport(m.width, m.height) activeHeight := height streamModel := m.streamModel @@ -319,7 +319,7 @@ func (m Model) View() string { } else { b.WriteString(renderHelpHint(width)) } - return common.ScreenStyle.Render(b.String()) + return tea.NewView(common.ScreenStyle.Render(b.String())) } func tickCmd(d time.Duration) tea.Cmd { diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go index 87b60e3..642c702 100644 --- a/internal/tui/dashboard/model_test.go +++ b/internal/tui/dashboard/model_test.go @@ -12,7 +12,7 @@ import ( "ior/internal/tui/eventstream" "ior/internal/tui/messages" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) type fakeSnapshotSource struct { @@ -28,31 +28,31 @@ func (f *fakeSnapshotSource) Snapshot() *statsengine.Snapshot { func TestKeySwitchingChangesActiveTab(t *testing.T) { m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap()) - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'2'}[0], Text: string([]rune{'2'})}) model := next.(Model) if model.activeTab != TabSyscalls { t.Fatalf("expected syscalls tab, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyTab}) + next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab}) model = next.(Model) if model.activeTab != TabFiles { t.Fatalf("expected next tab to be files, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftTab}) + next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab, Mod: tea.ModShift}) model = next.(Model) if model.activeTab != TabSyscalls { t.Fatalf("expected previous tab to be syscalls, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})}) model = next.(Model) if model.activeTab != TabStream { t.Fatalf("expected stream tab on key 7, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'6'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'6'}[0], Text: string([]rune{'6'})}) model = next.(Model) if model.activeTab != TabStream { t.Fatalf("expected stream tab on key 6, got %v", model.activeTab) @@ -62,25 +62,25 @@ func TestKeySwitchingChangesActiveTab(t *testing.T) { func TestArrowAndViKeysDoNotCycleTabs(t *testing.T) { m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap()) - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight}) + next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight}) model := next.(Model) if model.activeTab != TabOverview { t.Fatalf("expected right arrow not to change tabs, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'l'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'l'}[0], Text: string([]rune{'l'})}) model = next.(Model) if model.activeTab != TabOverview { t.Fatalf("expected l not to change tabs, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyLeft}) + next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft}) model = next.(Model) if model.activeTab != TabOverview { t.Fatalf("expected left arrow not to change tabs, got %v", model.activeTab) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'h'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'h'}[0], Text: string([]rune{'h'})}) model = next.(Model) if model.activeTab != TabOverview { t.Fatalf("expected h not to change tabs, got %v", model.activeTab) @@ -93,13 +93,13 @@ func TestSyscallsTabScrollsWithJK(t *testing.T) { snap := statsengine.NewSnapshot(nil, nil, nil, []statsengine.SyscallSnapshot{{Name: "read", Count: 1}, {Name: "write", Count: 1}}, nil, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{}) m.latest = &snap - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})}) model := next.(Model) if model.syscallsOffset != 1 { t.Fatalf("expected offset 1 after j, got %d", model.syscallsOffset) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})}) model = next.(Model) if model.syscallsOffset != 0 { t.Fatalf("expected offset 0 after k, got %d", model.syscallsOffset) @@ -112,13 +112,13 @@ func TestProcessesTabScrollsWithJK(t *testing.T) { snap := statsengine.NewSnapshot(nil, nil, nil, nil, nil, []statsengine.ProcessSnapshot{{PID: 1}, {PID: 2}}, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{}) m.latest = &snap - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})}) model := next.(Model) if model.processesOffset != 1 { t.Fatalf("expected processes offset 1 after j, got %d", model.processesOffset) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})}) model = next.(Model) if model.processesOffset != 0 { t.Fatalf("expected processes offset 0 after k, got %d", model.processesOffset) @@ -131,13 +131,13 @@ func TestFilesTabScrollsWithJK(t *testing.T) { snap := statsengine.NewSnapshot(nil, nil, nil, nil, []statsengine.FileSnapshot{{Path: "/a"}, {Path: "/b"}}, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{}) m.latest = &snap - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})}) model := next.(Model) if model.filesOffset != 1 { t.Fatalf("expected files offset 1 after j, got %d", model.filesOffset) } - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})}) model = next.(Model) if model.filesOffset != 0 { t.Fatalf("expected files offset 0 after k, got %d", model.filesOffset) @@ -155,7 +155,7 @@ func TestFilesTabGroupedScrollUsesDirectoryOffset(t *testing.T) { }, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{}) m.latest = &snap - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})}) model := next.(Model) if model.filesDirOffset != 1 { t.Fatalf("expected grouped dir offset 1 after j, got %d", model.filesDirOffset) @@ -171,7 +171,7 @@ func TestStreamSpaceUnpauseSchedulesStreamTick(t *testing.T) { m.activeTab = TabStream m.streamModel.HandleKey("space") // pause - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeySpace}) + next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeySpace}) _ = next if cmd == nil { t.Fatalf("expected stream tick command when unpausing stream") @@ -200,34 +200,34 @@ func TestStreamPausedSupportsJKArrowsAndPageKeys(t *testing.T) { m.streamModel.Refresh() _ = m.View() - next, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // pause + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeySpace}) // pause m = next.(Model) - before := rowFromStreamView(t, m.View()) + before := rowFromStreamView(t, m.View().Content) - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}}) + next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})}) m = next.(Model) - afterK := rowFromStreamView(t, m.View()) + afterK := rowFromStreamView(t, m.View().Content) if afterK >= before { t.Fatalf("expected k to scroll up while paused: before=%d afterK=%d", before, afterK) } - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown}) + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyDown}) m = next.(Model) - afterDown := rowFromStreamView(t, m.View()) + afterDown := rowFromStreamView(t, m.View().Content) if afterDown <= afterK { t.Fatalf("expected down arrow to scroll down while paused: afterK=%d afterDown=%d", afterK, afterDown) } - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgUp}) + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgUp}) m = next.(Model) - afterPgUp := rowFromStreamView(t, m.View()) + afterPgUp := rowFromStreamView(t, m.View().Content) if afterPgUp >= afterDown { t.Fatalf("expected pgup to scroll up while paused: afterDown=%d afterPgUp=%d", afterDown, afterPgUp) } - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgDown}) + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgDown}) m = next.(Model) - afterPgDown := rowFromStreamView(t, m.View()) + afterPgDown := rowFromStreamView(t, m.View().Content) if afterPgDown <= afterPgUp { t.Fatalf("expected pgdown to scroll down while paused: afterPgUp=%d afterPgDown=%d", afterPgUp, afterPgDown) } @@ -251,14 +251,14 @@ func TestDirGroupKeyTogglesOnlyOnFilesTab(t *testing.T) { m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap()) m.activeTab = TabFiles - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'d'}[0], Text: string([]rune{'d'})}) model := next.(Model) if !model.filesDirGrouped { t.Fatalf("expected filesDirGrouped to toggle on files tab") } model.activeTab = TabOverview - next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}}) + next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'d'}[0], Text: string([]rune{'d'})}) model = next.(Model) if !model.filesDirGrouped { t.Fatalf("expected filesDirGrouped unchanged outside files tab") @@ -272,7 +272,7 @@ func TestScrollOffsetDoesNotGrowUnbounded(t *testing.T) { m.latest = &snap for i := 0; i < 50; i++ { - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})}) m = next.(Model) } if m.syscallsOffset != 1 { @@ -284,7 +284,7 @@ func TestRefreshKeyEmitsRefreshTick(t *testing.T) { snap := &statsengine.Snapshot{TotalSyscalls: 13} engine := &fakeSnapshotSource{snap: snap} m := NewModelWithConfig(engine, nil, 250, common.DefaultKeyMap()) - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'r'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'r'}[0], Text: string([]rune{'r'})}) _ = next if cmd == nil { t.Fatalf("expected refresh command") @@ -366,7 +366,7 @@ func TestStatsTickClampsGroupedFilesOffset(t *testing.T) { func TestViewRendersTabBarAndHelp(t *testing.T) { m := NewModelWithConfig(nil, nil, 1000, common.DefaultKeyMap()) - out := m.View() + out := m.View().Content if !strings.Contains(out, "Overview") { t.Fatalf("expected overview label in view") } @@ -405,7 +405,7 @@ func TestStreamTabViewKeepsTabAndHelpChromeVisible(t *testing.T) { m.streamModel.SetSource(rb) m.streamModel.Refresh() - out := m.View() + out := m.View().Content if !strings.Contains(out, "1:Overview") { t.Fatalf("expected tab bar to remain visible in stream view") } @@ -416,21 +416,21 @@ func TestStreamTabViewKeepsTabAndHelpChromeVisible(t *testing.T) { func TestHelpToggleWithH(t *testing.T) { m := NewModelWithConfig(nil, nil, 1000, common.DefaultKeyMap()) - out := m.View() + out := m.View().Content if !strings.Contains(out, "press H for help") { t.Fatalf("expected default help hint") } - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'H'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'H'}[0], Text: string([]rune{'H'})}) m = next.(Model) - out = m.View() + out = m.View().Content if !strings.Contains(out, "tab next tab") { t.Fatalf("expected expanded help after pressing h") } - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'H'}}) + next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'H'}[0], Text: string([]rune{'H'})}) m = next.(Model) - out = m.View() + out = m.View().Content if !strings.Contains(out, "press H for help") { t.Fatalf("expected help hint after pressing h again") } diff --git a/internal/tui/dashboard/overview.go b/internal/tui/dashboard/overview.go index 5b8fab8..3ddeaf6 100644 --- a/internal/tui/dashboard/overview.go +++ b/internal/tui/dashboard/overview.go @@ -8,7 +8,7 @@ import ( "time" "unicode/utf8" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func renderOverview(snap *statsengine.Snapshot, width, height int) string { diff --git a/internal/tui/dashboard/overview_test.go b/internal/tui/dashboard/overview_test.go index 9895490..7de411c 100644 --- a/internal/tui/dashboard/overview_test.go +++ b/internal/tui/dashboard/overview_test.go @@ -7,7 +7,7 @@ import ( "ior/internal/statsengine" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func TestRenderOverviewIncludesCoreMetrics(t *testing.T) { diff --git a/internal/tui/dashboard/processes.go b/internal/tui/dashboard/processes.go index 281a86a..a9de382 100644 --- a/internal/tui/dashboard/processes.go +++ b/internal/tui/dashboard/processes.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/charmbracelet/bubbles/table" + "charm.land/bubbles/v2/table" ) func renderProcesses(snap *statsengine.Snapshot, width, height int) string { diff --git a/internal/tui/dashboard/syscalls.go b/internal/tui/dashboard/syscalls.go index 23fe37c..31f719e 100644 --- a/internal/tui/dashboard/syscalls.go +++ b/internal/tui/dashboard/syscalls.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - "github.com/charmbracelet/bubbles/table" + "charm.land/bubbles/v2/table" ) func renderSyscalls(snap *statsengine.Snapshot, width, height int) string { diff --git a/internal/tui/dashboard/tabs.go b/internal/tui/dashboard/tabs.go index df8f03e..62c7762 100644 --- a/internal/tui/dashboard/tabs.go +++ b/internal/tui/dashboard/tabs.go @@ -6,7 +6,7 @@ import ( "strings" "unicode/utf8" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) // Tab is a dashboard tab identifier. diff --git a/internal/tui/eventstream/exportmodal.go b/internal/tui/eventstream/exportmodal.go index cf020f7..70c57ee 100644 --- a/internal/tui/eventstream/exportmodal.go +++ b/internal/tui/eventstream/exportmodal.go @@ -3,9 +3,9 @@ package eventstream import ( "strings" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) type ExportModal struct { @@ -18,7 +18,7 @@ func NewExportModal() ExportModal { input := textinput.New() input.Prompt = "" input.CharLimit = 0 - input.Width = 44 + input.SetWidth(44) return ExportModal{textInput: input} } diff --git a/internal/tui/eventstream/filtermodal.go b/internal/tui/eventstream/filtermodal.go index f98db7f..4090925 100644 --- a/internal/tui/eventstream/filtermodal.go +++ b/internal/tui/eventstream/filtermodal.go @@ -5,9 +5,9 @@ import ( "strconv" "strings" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) type fieldKey int @@ -48,7 +48,7 @@ func NewFilterModal() FilterModal { input := textinput.New() input.Prompt = "" input.CharLimit = 0 - input.Width = 24 + input.SetWidth(24) m := FilterModal{textInput: input} m.fields = defaultFilterFields() @@ -112,7 +112,7 @@ func (m FilterModal) Update(msg tea.Msg) FilterModal { m.fields[m.activeField].opIndex = (m.fields[m.activeField].opIndex + 1) % len(compareOps) } return m - case " ": + case " ", "space": if !m.editing && m.fields[m.activeField].fieldKey == fieldErrorsOnly { if strings.TrimSpace(m.fields[m.activeField].value) == "true" { m.fields[m.activeField].value = "false" diff --git a/internal/tui/eventstream/filtermodal_test.go b/internal/tui/eventstream/filtermodal_test.go index ee53c82..a33cbb1 100644 --- a/internal/tui/eventstream/filtermodal_test.go +++ b/internal/tui/eventstream/filtermodal_test.go @@ -3,7 +3,7 @@ package eventstream import ( "testing" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) func TestFilterModalOpenClose(t *testing.T) { @@ -17,7 +17,7 @@ func TestFilterModalOpenClose(t *testing.T) { t.Fatalf("modal should be visible after open") } - m = m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) if m.Visible() { t.Fatalf("modal should close on esc") } @@ -29,11 +29,11 @@ func TestFilterModalNavigateFields(t *testing.T) { t.Fatalf("activeField=%d, want 0", m.activeField) } - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) if m.activeField != 1 { t.Fatalf("activeField=%d, want 1", m.activeField) } - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("k")}) + m = m.Update(tea.KeyPressMsg{Code: []rune("k")[0], Text: string([]rune("k"))}) if m.activeField != 0 { t.Fatalf("activeField=%d, want 0", m.activeField) } @@ -43,34 +43,34 @@ func TestFilterModalEditAndBuildFilter(t *testing.T) { m := NewFilterModal().Open(Filter{}) // Syscall = read - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("read")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: []rune("read")[0], Text: string([]rune("read"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) // PID >= 123 - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyTab}) // '=' -> '>' - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("123")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // '=' -> '>' + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: []rune("123")[0], Text: string([]rune("123"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) // Latency >= 1ms - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyTab}) // '=' -> '>=' - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("1ms")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // '=' -> '>=' + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) + m = m.Update(tea.KeyPressMsg{Code: []rune("1ms")[0], Text: string([]rune("1ms"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) // ErrorsOnly = true for m.activeField < len(m.fields)-1 { - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")}) + m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))}) } - m = m.Update(tea.KeyMsg{Type: tea.KeySpace}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeySpace}) - m = m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) if m.Visible() { t.Fatalf("modal should close on esc") } @@ -98,8 +98,8 @@ func TestFilterModalClearAll(t *testing.T) { } m := NewFilterModal().Open(initial) - m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("c")}) - m = m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + m = m.Update(tea.KeyPressMsg{Code: []rune("c")[0], Text: string([]rune("c"))}) + m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) f := m.Filter() if f.IsActive() { diff --git a/internal/tui/eventstream/model.go b/internal/tui/eventstream/model.go index d9c4ee3..cb05e47 100644 --- a/internal/tui/eventstream/model.go +++ b/internal/tui/eventstream/model.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) const ( @@ -354,7 +354,7 @@ func (m *Model) HandleKey(keyStr string) bool { // HandleTeaKey handles stream keys based on Bubble Tea key message types first, // then falls back to string matching for rune-driven shortcuts. func (m *Model) HandleTeaKey(msg tea.KeyMsg) bool { - switch msg.Type { + switch msg.Key().Code { case tea.KeyLeft: return m.HandleKey("left") case tea.KeyRight: @@ -373,9 +373,12 @@ func (m *Model) HandleTeaKey(msg tea.KeyMsg) bool { return m.HandleKey("esc") case tea.KeyEnter: return m.HandleKey("enter") - case tea.KeyRunes: - if len(msg.Runes) == 1 { - return m.HandleKey(string(msg.Runes[0])) + default: + if msg.Key().Text != "" { + runes := []rune(msg.Key().Text) + if len(runes) == 1 { + return m.HandleKey(msg.Key().Text) + } } } return m.HandleKey(msg.String()) @@ -810,23 +813,23 @@ func (m *Model) clampSelection() { func keyMsgFromString(keyStr string) tea.KeyMsg { switch keyStr { case "esc": - return tea.KeyMsg{Type: tea.KeyEsc} + return tea.KeyPressMsg{Code: tea.KeyEsc} case "enter": - return tea.KeyMsg{Type: tea.KeyEnter} + return tea.KeyPressMsg{Code: tea.KeyEnter} case "tab": - return tea.KeyMsg{Type: tea.KeyTab} + return tea.KeyPressMsg{Code: tea.KeyTab} case "up": - return tea.KeyMsg{Type: tea.KeyUp} + return tea.KeyPressMsg{Code: tea.KeyUp} case "down": - return tea.KeyMsg{Type: tea.KeyDown} + return tea.KeyPressMsg{Code: tea.KeyDown} case " ", "space": - return tea.KeyMsg{Type: tea.KeySpace} + return tea.KeyPressMsg{Code: tea.KeySpace, Text: " "} } if keyStr == "" { - return tea.KeyMsg{} + return tea.KeyPressMsg{} } runes := []rune(keyStr) - return tea.KeyMsg{Type: tea.KeyRunes, Runes: runes} + return tea.KeyPressMsg{Code: runes[0], Text: keyStr} } func rowNumber(start, total int) int { diff --git a/internal/tui/eventstream/render.go b/internal/tui/eventstream/render.go index 1f539c6..f93a63d 100644 --- a/internal/tui/eventstream/render.go +++ b/internal/tui/eventstream/render.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) type columnLayout struct { diff --git a/internal/tui/eventstream/render_test.go b/internal/tui/eventstream/render_test.go index b020edf..6240c69 100644 --- a/internal/tui/eventstream/render_test.go +++ b/internal/tui/eventstream/render_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - "github.com/charmbracelet/lipgloss" + "charm.land/lipgloss/v2" ) func TestRenderStatusAndFilterLines(t *testing.T) { diff --git a/internal/tui/eventstream/searchmodal.go b/internal/tui/eventstream/searchmodal.go index f744d00..94e9cd7 100644 --- a/internal/tui/eventstream/searchmodal.go +++ b/internal/tui/eventstream/searchmodal.go @@ -3,9 +3,9 @@ package eventstream import ( "strings" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) type SearchDirection int @@ -26,7 +26,7 @@ func NewSearchModal() SearchModal { input := textinput.New() input.Prompt = "" input.CharLimit = 0 - input.Width = 44 + input.SetWidth(44) return SearchModal{textInput: input, direction: SearchForward} } diff --git a/internal/tui/export/model.go b/internal/tui/export/model.go index 57612db..9c77080 100644 --- a/internal/tui/export/model.go +++ b/internal/tui/export/model.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) // Option is a selectable export target. diff --git a/internal/tui/export/model_test.go b/internal/tui/export/model_test.go index a97cd8b..2d47435 100644 --- a/internal/tui/export/model_test.go +++ b/internal/tui/export/model_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) func TestOpenAndClose(t *testing.T) { @@ -21,7 +21,7 @@ func TestOpenAndClose(t *testing.T) { func TestEnterEmitsRequest(t *testing.T) { m := NewModel().Open() - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) if cmd == nil { t.Fatalf("expected request command on enter") } @@ -40,7 +40,7 @@ func TestEnterEmitsRequest(t *testing.T) { func TestCancelOptionCloses(t *testing.T) { m := NewModel().Open() m.selected = len(optionValues) - 1 - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) if cmd != nil { t.Fatalf("expected no command when selecting cancel") } diff --git a/internal/tui/pidpicker/model.go b/internal/tui/pidpicker/model.go index 73f21ae..fff7614 100644 --- a/internal/tui/pidpicker/model.go +++ b/internal/tui/pidpicker/model.go @@ -6,10 +6,10 @@ import ( "ior/internal/tui/messages" "strings" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) const allPIDsLabel = "All PIDs" @@ -86,7 +86,7 @@ func NewPIDWithKeys(keys KeyMap) Model { input.Placeholder = "pid, comm, or cmdline" input.Focus() input.CharLimit = 0 - input.Width = 40 + input.SetWidth(40) return Model{ input: input, @@ -117,7 +117,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height - m.input.Width = clamp(msg.Width-16, 10, 100) + m.input.SetWidth(clamp(msg.Width-16, 10, 100)) return m, nil case processesLoadedMsg: m.processes = msg.processes @@ -138,17 +138,17 @@ func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, m.keys.Esc): return m, tea.Quit - case msg.Type == tea.KeyCtrlR: + case msg.Key().Mod&tea.ModCtrl != 0 && (msg.Key().Code == 'r' || msg.Key().Code == 'R'): return m, m.scanCmd() case key.Matches(msg, m.keys.Enter): return m, m.emitSelection() - case msg.Type == tea.KeyUp: + case msg.Key().Code == tea.KeyUp: if m.selectedIndex > 0 { m.selectedIndex-- } m.input.Blur() return m, nil - case msg.Type == tea.KeyDown: + case msg.Key().Code == tea.KeyDown: maxIndex := len(m.filtered) if m.selectedIndex < maxIndex { m.selectedIndex++ @@ -157,7 +157,7 @@ func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { return m, nil } - if msg.Type == tea.KeyRunes && !m.input.Focused() { + if msg.Key().Text != "" && !m.input.Focused() { if key.Matches(msg, m.keys.Refresh) { return m, m.scanCmd() } @@ -240,7 +240,7 @@ func cloneProcesses(in []ProcessInfo) []ProcessInfo { } // View renders the PID picker with filter input, list, and help bar. -func (m Model) View() string { +func (m Model) View() tea.View { var b strings.Builder if m.mode == PickerModeTID { if m.targetPID > 0 { @@ -265,7 +265,7 @@ func (m Model) View() string { b.WriteString("\n") b.WriteString(helpBarStyle.Render(renderHelp(m.keys.PickerShortHelp()))) - return screenStyle.Render(b.String()) + return tea.NewView(screenStyle.Render(b.String())) } func (m Model) renderRows() string { diff --git a/internal/tui/pidpicker/model_test.go b/internal/tui/pidpicker/model_test.go index 2d76508..c47e59b 100644 --- a/internal/tui/pidpicker/model_test.go +++ b/internal/tui/pidpicker/model_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) func TestApplyFilterByPIDCommAndCmdline(t *testing.T) { @@ -39,7 +39,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) { m.processes = []ProcessInfo{{Pid: 7, Comm: "vim"}, {Pid: 9, Comm: "top"}} m.applyFilter() - modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelAny msgAny := cmdAny() pidAny, ok := msgAny.(messages.PidSelectedMsg) @@ -51,7 +51,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) { } m.selectedIndex = 2 - modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelOne msgOne := cmdOne() pidOne, ok := msgOne.(messages.PidSelectedMsg) @@ -71,7 +71,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { } m.applyFilter() - modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelAny msgAny := cmdAny() tidAny, ok := msgAny.(messages.TidSelectedMsg) @@ -86,7 +86,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { } m.selectedIndex = 2 - modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) _ = modelOne msgOne := cmdOne() tidOne, ok := msgOne.(messages.TidSelectedMsg) @@ -104,7 +104,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) { func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { m := NewWithKeys(DefaultKeyMap()) - _, escCmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + _, escCmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) if escCmd == nil { t.Fatalf("expected esc to return quit cmd") } @@ -112,7 +112,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { t.Fatalf("expected quit msg from esc, got %T", msg) } - _, refreshCmd := m.Update(tea.KeyMsg{Type: tea.KeyCtrlR}) + _, refreshCmd := m.Update(tea.KeyPressMsg{Code: rune('r'), Text: "r", Mod: tea.ModCtrl}) if refreshCmd == nil { t.Fatalf("expected refresh cmd") } @@ -124,7 +124,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) { func TestRuneRDoesNotTriggerRefreshWhileFilterFocused(t *testing.T) { m := NewWithKeys(DefaultKeyMap()) - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'r'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'r'}[0], Text: string([]rune{'r'})}) if cmd == nil { t.Fatalf("expected textinput update cmd") } diff --git a/internal/tui/probes/model.go b/internal/tui/probes/model.go index 5cec2c7..c50c696 100644 --- a/internal/tui/probes/model.go +++ b/internal/tui/probes/model.go @@ -6,9 +6,9 @@ import ( "strings" "unicode/utf8" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/textinput" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) // Manager defines the probe operations used by the modal. @@ -45,7 +45,7 @@ func NewModel(manager Manager) Model { ti := textinput.New() ti.Prompt = "/ " ti.CharLimit = 0 - ti.Width = 28 + ti.SetWidth(28) return Model{ manager: manager, textInput: ti, @@ -110,7 +110,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.textInput.CursorEnd() m.textInput.Focus() return m, nil - case " ", "enter": + case " ", "space", "enter": selected := m.selectedSyscall() if selected == "" { return m, nil diff --git a/internal/tui/probes/model_test.go b/internal/tui/probes/model_test.go index 73a83bc..3a14675 100644 --- a/internal/tui/probes/model_test.go +++ b/internal/tui/probes/model_test.go @@ -5,7 +5,7 @@ import ( "ior/internal/probemanager" - tea "github.com/charmbracelet/bubbletea" + tea "charm.land/bubbletea/v2" ) type fakeManager struct { @@ -61,7 +61,7 @@ func TestToggleEmitsProbeToggledMsg(t *testing.T) { states: []probemanager.ProbeState{{Syscall: "read", Active: true}}, } m := NewModel(fm).Open() - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{' '}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{' '}[0], Text: string([]rune{' '})}) if cmd == nil { t.Fatalf("expected toggle command") } @@ -90,7 +90,7 @@ func TestBulkKeysApplyGloballyNotOnlyFiltered(t *testing.T) { m := NewModel(fm).Open() m.search = "read" - _, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'n'}}) + _, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'n'}[0], Text: string([]rune{'n'})}) if cmd == nil { t.Fatalf("expected bulk off command") } @@ -107,7 +107,7 @@ func TestBulkKeysApplyGloballyNotOnlyFiltered(t *testing.T) { m = NewModel(fm).Open() m.search = "read" fm.toggles = nil - _, cmd = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}}) + _, cmd = m.Update(tea.KeyPressMsg{Code: []rune{'a'}[0], Text: string([]rune{'a'})}) if cmd == nil { t.Fatalf("expected bulk on command") } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index bdd3ab5..24a8ba5 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -20,10 +20,10 @@ import ( "sync" "time" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/key" + "charm.land/bubbles/v2/spinner" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" ) // Screen identifies the currently active TUI screen. @@ -144,7 +144,7 @@ func Run() error { func RunWithTraceStarter(starter TraceStarter) error { cfg := flags.Get() model := newModelWithRuntimeConfig(cfg.PidFilter, cfg.PidFilter, cfg.TUIExportEnable, starter) - program := tea.NewProgram(model, tea.WithAltScreen()) + program := tea.NewProgram(model) _, err := program.Run() return err } @@ -227,9 +227,9 @@ func newModelWithRuntimeConfig(initialPID, startupPidFilter int, exportEnabled b func (m Model) Init() tea.Cmd { sizeCmd := initialWindowSizeCmd() if m.screen == ScreenDashboard && m.attaching { - return tea.Batch(sizeCmd, tea.WindowSize(), m.spin.Tick, m.beginTraceCmd()) + return tea.Batch(sizeCmd, tea.RequestWindowSize, m.spin.Tick, m.beginTraceCmd()) } - return tea.Batch(sizeCmd, tea.WindowSize(), m.pidPicker.Init()) + return tea.Batch(sizeCmd, tea.RequestWindowSize, m.pidPicker.Init()) } func initialWindowSizeCmd() tea.Cmd { @@ -455,40 +455,40 @@ func (m *Model) stopTrace() { } // View renders the currently active screen and startup overlay state. -func (m Model) View() string { +func (m Model) View() tea.View { if m.quitting { - return "" + return altScreenView("") } width, height := common.EffectiveViewport(m.width, m.height) if m.attaching { line := fmt.Sprintf("%s Attaching tracepoints...", m.spin.View()) - return placeToViewport(width, height, ScreenStyle.Render(PanelStyle.Render(line))) + return altScreenView(placeToViewport(width, height, ScreenStyle.Render(PanelStyle.Render(line)))) } if m.lastErr != nil { - return placeToViewport(width, height, ScreenStyle.Render(ErrorStyle.Render(m.lastErr.Error()))) + return altScreenView(placeToViewport(width, height, ScreenStyle.Render(ErrorStyle.Render(m.lastErr.Error())))) } switch m.screen { case ScreenPIDPicker: - base := m.pidPicker.View() + base := m.pidPicker.View().Content if m.exporter.Visible() { - return placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base) + return altScreenView(placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base)) } - return placeToViewport(width, height, base) + return altScreenView(placeToViewport(width, height, base)) case ScreenDashboard: - base := m.dashboard.View() + base := m.dashboard.View().Content if m.probeModal.Visible() { - return placeToViewport(width, height, m.probeModal.View(width, height)) + return altScreenView(placeToViewport(width, height, m.probeModal.View(width, height))) } if m.exporter.Visible() { - return placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base) + return altScreenView(placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base)) } - return placeToViewport(width, height, base) + return altScreenView(placeToViewport(width, height, base)) default: - return "" + return altScreenView("") } } @@ -657,3 +657,9 @@ func placeToViewport(width, height int, content string) string { } return lipgloss.Place(width, height, lipgloss.Left, lipgloss.Top, content) } + +func altScreenView(content string) tea.View { + view := tea.NewView(content) + view.AltScreen = true + return view +} diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go index 890dfc4..5c1ea5f 100644 --- a/internal/tui/tui_test.go +++ b/internal/tui/tui_test.go @@ -17,8 +17,8 @@ import ( "ior/internal/flags" "ior/internal/tui/probes" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" + "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" ) type fakeProbeManager struct { @@ -98,14 +98,14 @@ func TestTracingErrorMessageClearsAttachingState(t *testing.T) { func TestViewShowsAttachingAndErrorStates(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) m.attaching = true - attachingView := m.View() + attachingView := m.View().Content if !strings.Contains(attachingView, "Attaching tracepoints...") { t.Fatalf("expected attaching view, got %q", attachingView) } m.attaching = false m.lastErr = errors.New("failed") - errorView := m.View() + errorView := m.View().Content if !strings.Contains(errorView, "failed") { t.Fatalf("expected error view, got %q", errorView) } @@ -114,7 +114,7 @@ func TestViewShowsAttachingAndErrorStates(t *testing.T) { func TestQuitKeySetsQuittingState(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'q'}[0], Text: string([]rune{'q'})}) if cmd == nil { t.Fatalf("expected quit cmd") } @@ -132,9 +132,9 @@ func TestQuitKeyMatchesSingleBindingWithoutPanic(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) m.keys.Quit = key.NewBinding(key.WithKeys("x"), key.WithHelp("x", "quit")) - _, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'z'}}) + _, _ = m.Update(tea.KeyPressMsg{Code: []rune{'z'}[0], Text: string([]rune{'z'})}) - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'x'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'x'}[0], Text: string([]rune{'x'})}) if cmd == nil { t.Fatalf("expected quit cmd") } @@ -171,7 +171,7 @@ func TestQuitInvokesTraceStop(t *testing.T) { close(done) } - _, quitCmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}}) + _, quitCmd := m.Update(tea.KeyPressMsg{Code: []rune{'q'}[0], Text: string([]rune{'q'})}) if quitCmd == nil { t.Fatalf("expected quit command") } @@ -253,12 +253,12 @@ func TestTracingStartedRebindsEventStreamSource(t *testing.T) { next, _ = m.Update(tea.WindowSizeMsg{Width: 120, Height: 30}) m = next.(Model) - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}}) + next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})}) m = next.(Model) next, _ = m.Update(messages.StatsTickMsg{}) m = next.(Model) - if !strings.Contains(m.View(), "read") { + if !strings.Contains(m.View().Content, "read") { t.Fatalf("expected stream tab to render rebound stream event") } } @@ -271,7 +271,7 @@ func TestExportKeyOpensModalOnDashboard(t *testing.T) { m.screen = ScreenDashboard m.attaching = false - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})}) updated := next.(Model) if !updated.exporter.Visible() { t.Fatalf("expected export modal to open on e key") @@ -287,7 +287,7 @@ func TestSelectPIDKeyReturnsToFreshPickerAndStopsTrace(t *testing.T) { stopped := false m.traceStop = func() { stopped = true } - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'p'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'p'}[0], Text: string([]rune{'p'})}) updated := next.(Model) if !stopped { @@ -319,7 +319,7 @@ func TestSelectTIDKeyReturnsToPickerWhenPIDFilterIsAll(t *testing.T) { stopped := false m.traceStop = func() { stopped = true } - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'t'}[0], Text: string([]rune{'t'})}) updated := next.(Model) if !stopped { t.Fatalf("expected tracing stop before tid reselect") @@ -344,7 +344,7 @@ func TestSelectTIDKeyReturnsToPickerWhenSinglePIDSelected(t *testing.T) { stopped := false m.traceStop = func() { stopped = true } - next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t'}}) + next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'t'}[0], Text: string([]rune{'t'})}) updated := next.(Model) if !stopped { t.Fatalf("expected tracing stop before tid reselect") @@ -410,7 +410,7 @@ func TestExportKeyIgnoredWhenExportDisabled(t *testing.T) { m.screen = ScreenDashboard m.attaching = false - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})}) updated := next.(Model) if updated.exporter.Visible() { t.Fatalf("expected export modal to remain closed when export is disabled") @@ -427,23 +427,23 @@ func TestStreamFilterModalConsumesEKeyInsteadOfOpeningExport(t *testing.T) { m.width = 120 m.height = 30 - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})}) m = next.(Model) - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'f'}}) + next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'f'}[0], Text: string([]rune{'f'})}) m = next.(Model) - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyEnter}) + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) m = next.(Model) for _, r := range []rune{'o', 'p', 'e'} { - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{r}}) + next, _ = m.Update(tea.KeyPressMsg{Code: []rune{r}[0], Text: string([]rune{r})}) m = next.(Model) } - next, _ = m.Update(tea.KeyMsg{Type: tea.KeyEsc}) + next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) m = next.(Model) if m.exporter.Visible() { t.Fatalf("expected export modal to remain closed while stream filter modal handles typing") } - if !strings.Contains(m.View(), "syscall~ope") { + if !strings.Contains(m.View().Content, "syscall~ope") { t.Fatalf("expected typed syscall filter to be applied") } } @@ -475,7 +475,7 @@ func TestRunExportCmdCSVWritesFile(t *testing.T) { func TestHelpKeyDoesNotToggleOverlay(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'?'}[0], Text: string([]rune{'?'})}) updated := next.(Model) if updated.screen != ScreenPIDPicker { t.Fatalf("expected ? to have no effect, got screen %v", updated.screen) @@ -488,7 +488,7 @@ func TestViewShowsDashboardWithoutHelpOverlay(t *testing.T) { m.width = 100 m.height = 30 - out := m.View() + out := m.View().Content if !strings.Contains(out, "press H for help") { t.Fatalf("expected bottom help hint in dashboard") } @@ -498,7 +498,7 @@ func TestQuestionMarkDoesNotBlockUnderlyingActions(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) m.screen = ScreenDashboard - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})}) updated := next.(Model) if !updated.exporter.Visible() { t.Fatalf("expected export modal to open; ? overlay is removed") @@ -512,19 +512,19 @@ func TestQuestionMarkDoesNotBreakExportModalInput(t *testing.T) { m := NewModel(-1, func(context.Context) error { return nil }) m.screen = ScreenDashboard - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})}) updated := next.(Model) if !updated.exporter.Visible() { t.Fatalf("expected export modal to open") } - next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}}) + next, _ = updated.Update(tea.KeyPressMsg{Code: []rune{'?'}[0], Text: string([]rune{'?'})}) updated = next.(Model) if !updated.exporter.Visible() { t.Fatalf("expected export modal to remain open after ? key") } - next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyEsc}) + next, _ = updated.Update(tea.KeyPressMsg{Code: tea.KeyEsc}) updated = next.(Model) if updated.exporter.Visible() { t.Fatalf("expected esc to close export modal") @@ -540,7 +540,7 @@ func TestStatusBarHidesExportBindingWhenExportDisabled(t *testing.T) { m.width = 100 m.height = 30 - out := m.View() + out := m.View().Content if strings.Contains(out, "e snapshot export") { t.Fatalf("did not expect export shortcut in status bar when export is disabled") } @@ -568,21 +568,21 @@ func TestDashboardTabKeysChangeActiveView(t *testing.T) { m.width = 120 m.height = 30 - out := m.View() + out := m.View().Content if !strings.Contains(out, "Overview: waiting for stats") { t.Fatalf("expected overview waiting view by default") } - next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}}) + next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'2'}[0], Text: string([]rune{'2'})}) updated := next.(Model) - out = updated.View() + out = updated.View().Content if !strings.Contains(out, "Syscalls: waiting for stats") { t.Fatalf("expected syscalls waiting view after pressing 2") } - next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyTab}) + next, _ = updated.Update(tea.KeyPressMsg{Code: tea.KeyTab}) updated = next.(Model) - out = updated.View() + out = updated.View().Content if !strings.Contains(out, "Files: waiting for stats") { t.Fatalf("expected files waiting view after tab") } @@ -598,7 +598,7 @@ func TestProbeModalViewDoesNotStackDashboardContent(t *testing.T) { m.height = 30 m.probeModal = m.probeModal.Open() - out := m.View() + out := m.View().Content if !strings.Contains(out, "Probes (") { t.Fatalf("expected probe modal content, got %q", out) } |
