summaryrefslogtreecommitdiff
path: root/internal/tui/eventstream
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-08 22:35:34 +0200
committerPaul Buetow <paul@buetow.org>2026-03-08 22:35:34 +0200
commitd8bf918e83515f48564e0d0b98d30907944a1e0d (patch)
treeed155b0c365b0d322494a96ff799c3a59a1d9ec8 /internal/tui/eventstream
parent9f21f1004beeac10be9223bc8c5514261e397b6e (diff)
tui: unify table navigation and rendering
Diffstat (limited to 'internal/tui/eventstream')
-rw-r--r--internal/tui/eventstream/model.go56
-rw-r--r--internal/tui/eventstream/render.go88
-rw-r--r--internal/tui/eventstream/render_test.go2
3 files changed, 61 insertions, 85 deletions
diff --git a/internal/tui/eventstream/model.go b/internal/tui/eventstream/model.go
index 600d40d..2780524 100644
--- a/internal/tui/eventstream/model.go
+++ b/internal/tui/eventstream/model.go
@@ -6,6 +6,8 @@ import (
"strconv"
"strings"
+ "ior/internal/tui/common"
+
"charm.land/bubbles/v2/viewport"
tea "charm.land/bubbletea/v2"
)
@@ -321,7 +323,7 @@ func (m *Model) HandleKey(keyStr string) bool {
return true
case "G":
if m.paused {
- m.moveSelectionTo(len(m.filtered) - 1)
+ return m.handlePausedTableNavigation("G")
} else {
m.autoScroll = true
m.viewport.GotoBottom()
@@ -330,7 +332,7 @@ func (m *Model) HandleKey(keyStr string) bool {
return true
case "g":
if m.paused {
- m.moveSelectionTo(0)
+ return m.handlePausedTableNavigation("g")
} else {
m.autoScroll = false
m.viewport.GotoTop()
@@ -339,40 +341,38 @@ func (m *Model) HandleKey(keyStr string) bool {
return true
case "j", "down":
if m.paused {
- m.moveSelectionBy(1)
+ return m.handlePausedTableNavigation(keyStr)
} else {
m.handleViewportUpdate(keyMsgFromString("down"))
}
return true
case "k", "up":
if m.paused {
- m.moveSelectionBy(-1)
+ return m.handlePausedTableNavigation(keyStr)
} else {
m.handleViewportUpdate(keyMsgFromString("up"))
}
return true
case "left", "h":
if m.paused {
- m.moveSelectedColBy(-1)
- return true
+ return m.handlePausedTableNavigation(keyStr)
}
return m.handleViewportUpdate(keyMsgFromString("left"))
case "right", "l":
if m.paused {
- m.moveSelectedColBy(1)
- return true
+ return m.handlePausedTableNavigation(keyStr)
}
return m.handleViewportUpdate(keyMsgFromString("right"))
case "pgdown", "pgdn", "pagedown":
if m.paused {
- m.moveSelectionBy(m.pageStep())
+ return m.handlePausedTableNavigation(keyStr)
} else {
m.handleViewportUpdate(keyMsgFromString("pgdown"))
}
return true
case "pgup", "pageup":
if m.paused {
- m.moveSelectionBy(-m.pageStep())
+ return m.handlePausedTableNavigation(keyStr)
} else {
m.handleViewportUpdate(keyMsgFromString("pgup"))
}
@@ -597,6 +597,24 @@ func (m *Model) pageStep() int {
return rows - 1
}
+func (m *Model) handlePausedTableNavigation(keyStr string) bool {
+ if len(m.filtered) == 0 {
+ m.selectedIdx = -1
+ return true
+ }
+ m.ensureSelection()
+ m.ensureSelectedCol()
+ row := m.selectedIdx
+ col := m.selectedCol
+ if !common.HandleTableNavigationKey(keyStr, &row, &col, len(m.filtered), streamColumnCount, m.pageStep()) {
+ return false
+ }
+ m.selectedIdx = row
+ m.selectedCol = col
+ m.centerSelection()
+ return true
+}
+
func (m *Model) openFDTraceView() bool {
if m.fdTraceView.visible || m.selectedIdx < 0 || m.selectedIdx >= len(m.filtered) {
return false
@@ -666,16 +684,6 @@ func (m *Model) scrollFDTraceByLines(delta int) {
m.fdTraceView.offset = next
}
-func (m *Model) moveSelectionBy(delta int) {
- if len(m.filtered) == 0 {
- m.selectedIdx = -1
- return
- }
- m.ensureSelection()
- m.ensureSelectedCol()
- m.moveSelectionTo(m.selectedIdx + delta)
-}
-
func (m *Model) moveSelectionTo(idx int) {
if len(m.filtered) == 0 {
m.selectedIdx = -1
@@ -718,14 +726,6 @@ func (m *Model) ensureSelectedCol() {
}
}
-func (m *Model) moveSelectedColBy(delta int) {
- if delta == 0 {
- return
- }
- m.ensureSelectedCol()
- m.selectedCol = clamp(m.selectedCol+delta, 0, streamColumnCount-1)
-}
-
func (m *Model) requestGlobalFilterFromSelectedCell() bool {
if m.fdTraceView.visible || m.selectedIdx < 0 || m.selectedIdx >= len(m.filtered) {
return false
diff --git a/internal/tui/eventstream/render.go b/internal/tui/eventstream/render.go
index 819b6f4..4d7970b 100644
--- a/internal/tui/eventstream/render.go
+++ b/internal/tui/eventstream/render.go
@@ -23,21 +23,12 @@ type columnLayout struct {
file int
}
-var selectedRowStyle = lipgloss.NewStyle().
- Bold(true).
- Foreground(common.ColorBackground).
- Background(common.ColorPrimary)
-
-var selectedCellStyle = lipgloss.NewStyle().
- Bold(true).
- Foreground(common.ColorBackground).
- Background(common.ColorAccent)
-
func RenderStreamTable(width int, paused bool, totalCount, filteredCount, bufferLen, bufferCap int, filter Filter, filterStack []string, events []StreamEvent, selectedVisibleIdx int, selectedCol int) string {
if width <= 0 {
width = 100
}
contentWidth := panelContentWidth(width)
+ columns := streamColumns(contentWidth)
lines := make([]string, 0, len(events)+3)
lines = append(lines, renderStatusLine(paused, totalCount, filteredCount, bufferLen, bufferCap))
@@ -45,13 +36,9 @@ func RenderStreamTable(width int, paused bool, totalCount, filteredCount, buffer
if len(filterStack) > 0 {
lines = append(lines, renderFilterStackLine(filterStack))
}
- lines = append(lines, renderColumnHeader(contentWidth))
+ lines = append(lines, common.RenderTableHeader(columns))
for i, ev := range events {
- col := -1
- if i == selectedVisibleIdx {
- col = selectedCol
- }
- lines = append(lines, renderEventRow(ev, contentWidth, i == selectedVisibleIdx, col))
+ lines = append(lines, renderEventRow(ev, columns, i == selectedVisibleIdx, selectedCol))
}
return common.PanelStyle.Width(contentWidth).Render(strings.Join(lines, "\n"))
@@ -66,9 +53,10 @@ func RenderFDTraceTable(width int, pid uint32, fd int32, totalCount int, events
lines := make([]string, 0, len(events)+3)
lines = append(lines, common.HeaderStyle.Render("FD Trace (ring snapshot)"))
lines = append(lines, fmt.Sprintf("PID:%d FD:%d matched:%d", pid, fd, totalCount))
- lines = append(lines, renderColumnHeader(contentWidth))
+ columns := streamColumns(contentWidth)
+ lines = append(lines, common.RenderTableHeader(columns))
for _, ev := range events {
- lines = append(lines, renderEventRow(ev, contentWidth, false, -1))
+ lines = append(lines, renderEventRow(ev, columns, false, -1))
}
return common.PanelStyle.Width(contentWidth).Render(strings.Join(lines, "\n"))
@@ -98,55 +86,43 @@ func renderFilterStackLine(filterStack []string) string {
return common.HeaderStyle.Render("Stack:") + " " + strings.Join(filterStack, " | ")
}
-func renderColumnHeader(width int) string {
+func streamColumns(width int) []common.TableColumn {
cols := computeColumnLayout(width)
- header := fmt.Sprintf("%-*s %-*s %-*s %-*s %-*s %-*s %-*s %-*s %-*s %s",
- cols.gap, "Gap",
- cols.latency, "Latency",
- cols.comm, "Comm",
- cols.pid, "PID",
- cols.tid, "TID",
- cols.syscall, "Syscall",
- cols.fd, "FD",
- cols.ret, "Ret",
- cols.bytes, "Bytes",
- "File",
- )
- return common.HelpBarStyle.Render(header)
+ return []common.TableColumn{
+ {Title: "Gap", Width: cols.gap},
+ {Title: "Latency", Width: cols.latency},
+ {Title: "Comm", Width: cols.comm},
+ {Title: "PID", Width: cols.pid},
+ {Title: "TID", Width: cols.tid},
+ {Title: "Syscall", Width: cols.syscall},
+ {Title: "FD", Width: cols.fd},
+ {Title: "Ret", Width: cols.ret},
+ {Title: "Bytes", Width: cols.bytes},
+ {Title: "File", Width: cols.file},
+ }
}
-func renderEventRow(ev StreamEvent, width int, selected bool, selectedCol int) string {
- cols := computeColumnLayout(width)
+func renderEventRow(ev StreamEvent, columns []common.TableColumn, selected bool, selectedCol int) string {
fd := "-"
if ev.FD >= 0 {
fd = strconv.FormatInt(int64(ev.FD), 10)
}
cells := []string{
- fmt.Sprintf("%-*s", cols.gap, fitCell(formatDurationNs(ev.GapNs), cols.gap)),
- fmt.Sprintf("%-*s", cols.latency, fitCell(formatDurationNs(ev.DurationNs), cols.latency)),
- fmt.Sprintf("%-*s", cols.comm, fitCell(ev.Comm, cols.comm)),
- fmt.Sprintf("%-*s", cols.pid, fitCell(strconv.FormatUint(uint64(ev.PID), 10), cols.pid)),
- fmt.Sprintf("%-*s", cols.tid, fitCell(strconv.FormatUint(uint64(ev.TID), 10), cols.tid)),
- fmt.Sprintf("%-*s", cols.syscall, fitCell(ev.Syscall, cols.syscall)),
- fmt.Sprintf("%-*s", cols.fd, fitCell(fd, cols.fd)),
- fmt.Sprintf("%-*s", cols.ret, fitCell(strconv.FormatInt(ev.RetVal, 10), cols.ret)),
- fmt.Sprintf("%-*s", cols.bytes, fitCell(strconv.FormatUint(ev.Bytes, 10), cols.bytes)),
- fitCell(ev.FileName, cols.file),
- }
- if selected {
- for i := range cells {
- if i == selectedCol {
- cells[i] = selectedCellStyle.Render(cells[i])
- } else {
- cells[i] = selectedRowStyle.Render(cells[i])
- }
- }
- return strings.Join(cells, " ")
+ fitCell(formatDurationNs(ev.GapNs), columns[0].Width),
+ fitCell(formatDurationNs(ev.DurationNs), columns[1].Width),
+ fitCell(ev.Comm, columns[2].Width),
+ fitCell(strconv.FormatUint(uint64(ev.PID), 10), columns[3].Width),
+ fitCell(strconv.FormatUint(uint64(ev.TID), 10), columns[4].Width),
+ fitCell(ev.Syscall, columns[5].Width),
+ fitCell(fd, columns[6].Width),
+ fitCell(strconv.FormatInt(ev.RetVal, 10), columns[7].Width),
+ fitCell(strconv.FormatUint(ev.Bytes, 10), columns[8].Width),
+ fitCell(ev.FileName, columns[9].Width),
}
if ev.IsError {
- return common.ErrorStyle.Render(strings.Join(cells, " "))
+ return common.RenderTableRow(columns, cells, selected, selectedCol, common.ErrorStyle)
}
- return strings.Join(cells, " ")
+ return common.RenderTableRow(columns, cells, selected, selectedCol, lipgloss.Style{})
}
func computeColumnLayout(width int) columnLayout {
diff --git a/internal/tui/eventstream/render_test.go b/internal/tui/eventstream/render_test.go
index d5b2af5..a5d7d95 100644
--- a/internal/tui/eventstream/render_test.go
+++ b/internal/tui/eventstream/render_test.go
@@ -95,7 +95,7 @@ func TestRenderEventRowIsSingleLineWithControlCharsAndLongValues(t *testing.T) {
RetVal: -9223372036854775808,
}
- row := renderEventRow(ev, 80, false, -1)
+ row := renderEventRow(ev, streamColumns(80), false, -1)
if strings.Contains(row, "\n") || strings.Contains(row, "\r") || strings.Contains(row, "\t") {
t.Fatalf("expected a sanitized single-line row, got %q", row)
}