From 0b4af81203fd8acc4ef79404725d7c14667fd7ac Mon Sep 17 00:00:00 2001 From: Nils Stinnesbeck Date: Tue, 20 Oct 2026 19:18:24 +0200 Subject: [PATCH] Fix UI freeze when pressing Down in an empty table tview's table navigation never terminates when no row is selectable. Turn selection off while the Downloads panels and the release list are empty. Co-Authored-By: Claude Sonnet 5.5 --- internal/ui/downloads.go | 4 ++++ internal/ui/releases.go | 2 ++ internal/ui/table_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 internal/ui/table_test.go diff --git a/internal/ui/downloads.go b/internal/ui/downloads.go index 29efb8f..efe3ff6 100644 --- a/internal/ui/downloads.go +++ b/internal/ui/downloads.go @@ -293,10 +293,14 @@ func fill(t *tview.Table, expand int, rows [][]string) { // shows placeholder text when there are no rows. func restoreSelection(t *tview.Table, n, selRow int, placeholder string) { if n == 0 { + // With no selectable row, tview's Down/Up handling never terminates, + // so turn selection off entirely. t.SetCell(1, 0, tview.NewTableCell("[gray]"+placeholder).SetSelectable(false)) + t.SetSelectable(false, false) t.Select(0, 0) return } + t.SetSelectable(true, false) t.Select(max(selRow, 1), 0) } diff --git a/internal/ui/releases.go b/internal/ui/releases.go index 555b0fa..aafab92 100644 --- a/internal/ui/releases.go +++ b/internal/ui/releases.go @@ -166,6 +166,8 @@ func (v *releasesView) render() { } v.table.SetCell(1, 1, tview.NewTableCell("[gray]"+msg).SetSelectable(false)) } + // With no selectable row tview's Down/Up handling never terminates. + v.table.SetSelectable(len(v.shown) > 0, false) if len(v.shown) > 0 { v.table.Select(max(selRow, 1), 0) } else { diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go new file mode 100644 index 0000000..8ada251 --- /dev/null +++ b/internal/ui/table_test.go @@ -0,0 +1,38 @@ +package ui + +import ( + "testing" + "time" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" +) + +// Pressing Down in an empty table used to loop forever in tview because the +// placeholder row is not selectable. +func TestEmptyTableDownDoesNotHang(t *testing.T) { + tbl := tview.NewTable().SetSelectable(true, false).SetFixed(1, 0) + restoreSelection(tbl, 0, 0, "empty") + + screen := tcell.NewSimulationScreen("") + if err := screen.Init(); err != nil { + t.Fatal(err) + } + defer screen.Fini() + screen.SetSize(80, 10) + tbl.SetRect(0, 0, 80, 10) + + done := make(chan struct{}) + go func() { + tbl.Draw(screen) + tbl.InputHandler()(tcell.NewEventKey(tcell.KeyDown, 0, 0), func(tview.Primitive) {}) + tbl.InputHandler()(tcell.NewEventKey(tcell.KeyUp, 0, 0), func(tview.Primitive) {}) + tbl.Draw(screen) + close(done) + }() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("table input handler hung on an empty table") + } +}