diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-25 22:58:40 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-25 22:58:40 +0200 |
| commit | 4c34b9efcd539c819648c927d7e3f53220df8ad2 (patch) | |
| tree | f9de9fd650a2d16316ba2c159990d891c9de5189 /internal/tui/dashboard | |
| parent | 67e10f34c92e93343adbd690b3b21e455e863bd3 (diff) | |
Fix stream paused scrolling and apply pending TUI/probe updates
Diffstat (limited to 'internal/tui/dashboard')
| -rw-r--r-- | internal/tui/dashboard/model.go | 13 | ||||
| -rw-r--r-- | internal/tui/dashboard/model_test.go | 85 |
2 files changed, 95 insertions, 3 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go index 8b2c814..9b425b1 100644 --- a/internal/tui/dashboard/model.go +++ b/internal/tui/dashboard/model.go @@ -73,6 +73,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height + m.streamModel.SetViewport(msg.Width, msg.Height) return m, nil case refreshTickMsg: snap := m.snapshot() @@ -104,7 +105,10 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { prevActiveTab := m.activeTab var cmd tea.Cmd keyStr := msg.String() - handled := m.handleArrowTabKey(keyStr) || m.handleScrollKey(keyStr) + handled := m.handleArrowTabKey(keyStr) || m.handleScrollKey(msg) + if handled && m.activeTab == TabStream && (keyStr == " " || keyStr == "space") && !m.streamModel.Paused() { + cmd = streamTickCmd() + } if !handled { switch { @@ -171,7 +175,8 @@ func (m *Model) handleArrowTabKey(keyStr string) bool { } } -func (m *Model) handleScrollKey(keyStr string) bool { +func (m *Model) handleScrollKey(msg tea.KeyMsg) bool { + keyStr := msg.String() switch m.activeTab { case TabSyscalls: return scrollOffset(keyStr, &m.syscallsOffset, m.maxSyscallsRows()) @@ -183,7 +188,9 @@ func (m *Model) handleScrollKey(keyStr string) bool { case TabProcesses: return scrollOffset(keyStr, &m.processesOffset, m.maxProcessesRows()) case TabStream: - return m.streamModel.HandleKey(keyStr) + streamWidth, streamHeight := common.EffectiveViewport(m.width, m.height) + m.streamModel.SetViewport(streamWidth, streamHeight) + return m.streamModel.HandleTeaKey(msg) default: return false } diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go index c1b2e1d..1e54b27 100644 --- a/internal/tui/dashboard/model_test.go +++ b/internal/tui/dashboard/model_test.go @@ -1,11 +1,15 @@ package dashboard import ( + "fmt" + "regexp" + "strconv" "strings" "testing" "ior/internal/statsengine" common "ior/internal/tui/common" + "ior/internal/tui/eventstream" "ior/internal/tui/messages" tea "github.com/charmbracelet/bubbletea" @@ -161,6 +165,87 @@ func TestFilesTabGroupedScrollUsesDirectoryOffset(t *testing.T) { } } +func TestStreamSpaceUnpauseSchedulesStreamTick(t *testing.T) { + rb := eventstream.NewRingBuffer() + m := NewModelWithConfig(nil, rb, 250, common.DefaultKeyMap()) + m.activeTab = TabStream + m.streamModel.HandleKey("space") // pause + + next, cmd := m.Update(tea.KeyMsg{Type: tea.KeySpace}) + _ = next + if cmd == nil { + t.Fatalf("expected stream tick command when unpausing stream") + } +} + +func TestStreamPausedSupportsJKArrowsAndPageKeys(t *testing.T) { + rb := eventstream.NewRingBuffer() + for i := 0; i < 300; i++ { + rb.Push(eventstream.StreamEvent{ + Seq: uint64(i + 1), + Syscall: "read", + Comm: "proc", + PID: 1000, + TID: uint32(2000 + i), + FileName: fmt.Sprintf("/tmp/file-%03d", i), + }) + } + + m := NewModelWithConfig(nil, rb, 250, common.DefaultKeyMap()) + m.activeTab = TabStream + next, _ := m.Update(tea.WindowSizeMsg{Width: 120, Height: 30}) + m = next.(Model) + + m.streamModel.Refresh() + _ = m.View() + + next, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // pause + m = next.(Model) + before := rowFromStreamView(t, m.View()) + + next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}}) + m = next.(Model) + afterK := rowFromStreamView(t, m.View()) + 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}) + m = next.(Model) + afterDown := rowFromStreamView(t, m.View()) + 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}) + m = next.(Model) + afterPgUp := rowFromStreamView(t, m.View()) + 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}) + m = next.(Model) + afterPgDown := rowFromStreamView(t, m.View()) + if afterPgDown <= afterPgUp { + t.Fatalf("expected pgdown to scroll down while paused: afterPgUp=%d afterPgDown=%d", afterPgUp, afterPgDown) + } +} + +func rowFromStreamView(t *testing.T, view string) int { + t.Helper() + re := regexp.MustCompile(`Row ([0-9]+)/([0-9]+)`) + m := re.FindStringSubmatch(view) + if len(m) != 3 { + t.Fatalf("stream row status not found in view") + } + row, err := strconv.Atoi(m[1]) + if err != nil { + t.Fatalf("invalid row value %q: %v", m[1], err) + } + return row +} + func TestDirGroupKeyTogglesOnlyOnFilesTab(t *testing.T) { m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap()) m.activeTab = TabFiles |
