diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-25 09:15:18 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-25 09:15:18 +0200 |
| commit | b3625cc67c81b4c1bd654a9fcdaf624d76306b07 (patch) | |
| tree | 0a57bf7e5ec4dfbca9109435248d4d04f77e8015 /internal | |
| parent | 22f1589e62aeafed805b8dd07d4610b7662f205e (diff) | |
Clamp stream rows to single-line panel width
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/tui/eventstream/render.go | 44 | ||||
| -rw-r--r-- | internal/tui/eventstream/render_test.go | 22 |
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) + } +} |
