summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-25 09:15:18 +0200
committerPaul Buetow <paul@buetow.org>2026-02-25 09:15:18 +0200
commitb3625cc67c81b4c1bd654a9fcdaf624d76306b07 (patch)
tree0a57bf7e5ec4dfbca9109435248d4d04f77e8015 /internal
parent22f1589e62aeafed805b8dd07d4610b7662f205e (diff)
Clamp stream rows to single-line panel width
Diffstat (limited to 'internal')
-rw-r--r--internal/tui/eventstream/render.go44
-rw-r--r--internal/tui/eventstream/render_test.go22
2 files changed, 55 insertions, 11 deletions
diff --git a/internal/tui/eventstream/render.go b/internal/tui/eventstream/render.go
index 3fd2d26..1f748a3 100644
--- a/internal/tui/eventstream/render.go
+++ b/internal/tui/eventstream/render.go
@@ -22,13 +22,14 @@ func RenderStreamTable(width int, paused bool, totalCount, filteredCount, buffer
if width <= 0 {
width = 100
}
+ contentWidth := panelContentWidth(width)
lines := make([]string, 0, len(events)+3)
lines = append(lines, renderStatusLine(paused, totalCount, filteredCount, bufferLen, bufferCap))
lines = append(lines, renderFilterLine(filter))
- lines = append(lines, renderColumnHeader(width))
+ lines = append(lines, renderColumnHeader(contentWidth))
for _, ev := range events {
- lines = append(lines, renderEventRow(ev, width))
+ lines = append(lines, renderEventRow(ev, contentWidth))
}
return common.PanelStyle.Width(width).Render(strings.Join(lines, "\n"))
@@ -72,15 +73,15 @@ func renderColumnHeader(width int) string {
func renderEventRow(ev StreamEvent, width int) string {
cols := computeColumnLayout(width)
pidTid := fmt.Sprintf("%d.%d", ev.PID, ev.TID)
- row := fmt.Sprintf("%-*s %-*s %-*s %-*s %-*s %-*d %-*d %s",
- cols.gap, formatDurationNs(ev.GapNs),
- cols.latency, formatDurationNs(ev.DurationNs),
- cols.comm, truncateMiddle(ev.Comm, cols.comm),
- cols.pidTid, truncateMiddle(pidTid, cols.pidTid),
- cols.syscall, truncateMiddle(ev.Syscall, cols.syscall),
- cols.ret, ev.RetVal,
- cols.bytes, ev.Bytes,
- truncateMiddle(ev.FileName, cols.file),
+ row := fmt.Sprintf("%-*s %-*s %-*s %-*s %-*s %-*s %-*s %s",
+ cols.gap, fitCell(formatDurationNs(ev.GapNs), cols.gap),
+ cols.latency, fitCell(formatDurationNs(ev.DurationNs), cols.latency),
+ cols.comm, fitCell(ev.Comm, cols.comm),
+ cols.pidTid, fitCell(pidTid, cols.pidTid),
+ cols.syscall, fitCell(ev.Syscall, cols.syscall),
+ cols.ret, fitCell(strconv.FormatInt(ev.RetVal, 10), cols.ret),
+ cols.bytes, fitCell(strconv.FormatUint(ev.Bytes, 10), cols.bytes),
+ fitCell(ev.FileName, cols.file),
)
if ev.IsError {
return common.ErrorStyle.Render(row)
@@ -155,3 +156,24 @@ func truncateMiddle(path string, limit int) string {
}
return path[:head] + "..." + path[len(path)-tail:]
}
+
+func fitCell(s string, width int) string {
+ return truncateMiddle(sanitizeOneLine(s), width)
+}
+
+func sanitizeOneLine(s string) string {
+ s = strings.ReplaceAll(s, "\n", " ")
+ s = strings.ReplaceAll(s, "\r", " ")
+ s = strings.ReplaceAll(s, "\t", " ")
+ return s
+}
+
+func panelContentWidth(width int) int {
+ // common.PanelStyle uses 1-char border on each side and 1-char horizontal
+ // padding on each side: subtract 4 from total width for content.
+ inner := width - 4
+ if inner < 20 {
+ return 20
+ }
+ return inner
+}
diff --git a/internal/tui/eventstream/render_test.go b/internal/tui/eventstream/render_test.go
index 5f037ab..89c2029 100644
--- a/internal/tui/eventstream/render_test.go
+++ b/internal/tui/eventstream/render_test.go
@@ -71,3 +71,25 @@ func TestFormatDurationNs(t *testing.T) {
}
}
}
+
+func TestRenderEventRowIsSingleLineWithControlCharsAndLongValues(t *testing.T) {
+ ev := StreamEvent{
+ Syscall: "very_very_long_syscall_name_that_would_otherwise_overflow",
+ Comm: "cmd\twith\tcontrols",
+ PID: 1234567,
+ TID: 7654321,
+ DurationNs: 123456789012345,
+ GapNs: 9988776655443322,
+ Bytes: 18446744073709551615,
+ FileName: "/very/long/path/with/newline\nand\ttabs/that/should/not/wrap",
+ RetVal: -9223372036854775808,
+ }
+
+ row := renderEventRow(ev, 80)
+ if strings.Contains(row, "\n") || strings.Contains(row, "\r") || strings.Contains(row, "\t") {
+ t.Fatalf("expected a sanitized single-line row, got %q", row)
+ }
+ if !strings.Contains(row, "...") {
+ t.Fatalf("expected truncation ellipsis in narrow row, got %q", row)
+ }
+}