summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/tui/flamegraph/frame_animator.go106
-rw-r--r--internal/tui/flamegraph/frame_animator_test.go6
-rw-r--r--internal/tui/flamegraph/model.go7
-rw-r--r--internal/tui/flamegraph/renderer.go95
4 files changed, 102 insertions, 112 deletions
diff --git a/internal/tui/flamegraph/frame_animator.go b/internal/tui/flamegraph/frame_animator.go
index 86e52ec..80b594a 100644
--- a/internal/tui/flamegraph/frame_animator.go
+++ b/internal/tui/flamegraph/frame_animator.go
@@ -79,112 +79,6 @@ func (fa *FrameAnimator) reset() {
fa.ancestry = frameAncestry{}
}
-// frameIndexAt returns the index of the frame rendered at terminal coordinates
-// (x, y), or -1 if no frame occupies that cell. showHelp adds one extra line
-// to the UI chrome so the frame area row calculations account for it.
-func frameIndexAt(frames []tuiFrame, x, y, width, height int, showHelp, heightMetricActive bool) int {
- if len(frames) == 0 || width <= 0 || height <= 0 {
- return -1
- }
- if x < 0 || x >= width || y < 0 {
- return -1
- }
- extraLines := 1 // selection status line
- if showHelp {
- extraLines++
- }
- renderHeight := height - extraLines
- if renderHeight < 3 {
- renderHeight = 3
- }
- availableRows := renderHeight - 2 // flame toolbar + frame-status line
- if availableRows < 1 {
- return -1
- }
- // Row 0 is flame toolbar, rows 1..availableRows are bars, last row is status.
- if y < 1 || y > availableRows {
- return -1
- }
- targetRow := frameCoordToTargetRow(frames, y-1, availableRows, heightMetricActive)
- if targetRow < 0 {
- return -1
- }
- return findFrameAtRow(frames, targetRow, x, width)
-}
-
-// frameCoordToTargetRow converts a data-area row offset (0-based, after
-// stripping the toolbar row) into the logical frame row index. Returns -1 when
-// the coordinate falls in the top padding above the first visible row.
-func frameCoordToTargetRow(frames []tuiFrame, dataRow, availableRows int, heightMetricActive bool) int {
- maxRow := maxFrameRowForSet(frames, nil)
- barHeight := computeBarHeight(availableRows, maxRow+1, maxBarVisualHeight)
- leafBarHeight := barHeight
- visibleDepthRows := availableRows / barHeight
- if heightMetricActive {
- barHeight = 1
- visibleDepthRows = availableRows
- }
- if visibleDepthRows < 1 {
- visibleDepthRows = 1
- }
- rowOffset := 0
- if maxRow+1 > visibleDepthRows {
- rowOffset = maxRow + 1 - visibleDepthRows
- }
- if heightMetricActive {
- visibleNonLeafRows := max(0, maxRow-rowOffset)
- leafBarHeight = availableRows - visibleNonLeafRows
- if leafBarHeight < 1 {
- leafBarHeight = 1
- }
- }
- renderedRows := (maxRow - rowOffset + 1) * barHeight
- if heightMetricActive {
- renderedRows = leafBarHeight + max(0, maxRow-rowOffset)*barHeight
- }
- padTop := 0
- if renderedRows < availableRows {
- padTop = availableRows - renderedRows
- }
- if dataRow < padTop {
- return -1
- }
- rowInRender := dataRow - padTop
- for row := maxRow; row >= rowOffset; row-- {
- rowHeight := barHeight
- if heightMetricActive && row == maxRow {
- rowHeight = leafBarHeight
- }
- if rowInRender < rowHeight {
- return row
- }
- rowInRender -= rowHeight
- }
- return -1
-}
-
-// findFrameAtRow scans frames for the narrowest one that occupies logical row
-// targetRow and contains pixel column x within [0, width). Returning the
-// narrowest frame resolves overlap between wide parent and narrow child bars.
-func findFrameAtRow(frames []tuiFrame, targetRow, x, width int) int {
- best := -1
- bestWidth := int(^uint(0) >> 1)
- for idx, frame := range frames {
- if frame.Row != targetRow || frame.Col >= width {
- continue
- }
- right := min(width, frame.Col+frame.Width)
- if x < frame.Col || x >= right {
- continue
- }
- if frame.Width < bestWidth {
- best = idx
- bestWidth = frame.Width
- }
- }
- return best
-}
-
// driveWindowActive reports whether lastKeyAt falls within the active drive
// window where the user is considered to be actively pressing keys.
func driveWindowActive(lastKeyAt time.Time) bool {
diff --git a/internal/tui/flamegraph/frame_animator_test.go b/internal/tui/flamegraph/frame_animator_test.go
index 6b4d4f7..acc4f26 100644
--- a/internal/tui/flamegraph/frame_animator_test.go
+++ b/internal/tui/flamegraph/frame_animator_test.go
@@ -10,9 +10,10 @@ func TestFrameCoordToTargetRowKeepsUniformBarMapping(t *testing.T) {
{Name: "leaf", Row: 3, Col: 0, Width: 20, Path: "root" + pathSeparator + "a" + pathSeparator + "b" + pathSeparator + "leaf"},
}
availableRows := 8
+ params := computeRenderParamsForAvailableRows(frames, availableRows, false)
want := []int{3, 3, 2, 2, 1, 1, 0, 0}
for dataRow, expected := range want {
- if got := frameCoordToTargetRow(frames, dataRow, availableRows, false); got != expected {
+ if got := frameCoordToTargetRow(dataRow, params); got != expected {
t.Fatalf("dataRow=%d: got row=%d want=%d", dataRow, got, expected)
}
}
@@ -24,9 +25,10 @@ func TestFrameCoordToTargetRowHeightMetricMapsExpandedLeafBand(t *testing.T) {
{Name: "leaf", Row: 1, Col: 0, Width: 20, Path: "root" + pathSeparator + "leaf", HeightTotal: 100},
}
availableRows := 6
+ params := computeRenderParamsForAvailableRows(frames, availableRows, true)
want := []int{1, 1, 1, 1, 1, 0}
for dataRow, expected := range want {
- if got := frameCoordToTargetRow(frames, dataRow, availableRows, true); got != expected {
+ if got := frameCoordToTargetRow(dataRow, params); got != expected {
t.Fatalf("dataRow=%d: got row=%d want=%d", dataRow, got, expected)
}
}
diff --git a/internal/tui/flamegraph/model.go b/internal/tui/flamegraph/model.go
index b9a8734..9f14a70 100644
--- a/internal/tui/flamegraph/model.go
+++ b/internal/tui/flamegraph/model.go
@@ -1098,15 +1098,16 @@ func (m Model) rootSnapshotPath() string {
return m.ZoomNavigator.rootSnapshotPath(m.snapshot, m.frames)
}
-// frameIndexAt delegates to the FrameAnimator package-level helper to convert
+// frameIndexAt delegates to the renderer package-level helper to convert
// terminal coordinates (x, y) to a frame index, accounting for UI chrome.
func (m Model) frameIndexAt(x, y int) int {
return frameIndexAt(m.frames, x, y, m.width, m.height, m.showHelp, strings.TrimSpace(m.heightField) != "")
}
-// frameCoordToTargetRow delegates to the FrameAnimator package-level helper.
+// frameCoordToTargetRow delegates to the renderer package-level helper.
func (m Model) frameCoordToTargetRow(dataRow, availableRows int) int {
- return frameCoordToTargetRow(m.frames, dataRow, availableRows, strings.TrimSpace(m.heightField) != "")
+ params := computeRenderParamsForAvailableRows(m.frames, availableRows, strings.TrimSpace(m.heightField) != "")
+ return frameCoordToTargetRow(dataRow, params)
}
func (m Model) withZoomLineage(frames []tuiFrame) []tuiFrame {
diff --git a/internal/tui/flamegraph/renderer.go b/internal/tui/flamegraph/renderer.go
index a38792e..6d5a494 100644
--- a/internal/tui/flamegraph/renderer.go
+++ b/internal/tui/flamegraph/renderer.go
@@ -231,6 +231,7 @@ type renderViewParams struct {
availableRows int
visibleFrames int
truncated bool
+ heightMetric bool
}
// RenderContext bundles flamegraph render inputs to avoid long positional
@@ -270,7 +271,15 @@ type renderRowsContext struct {
// computeRenderParams derives the row-layout parameters for a given frame set
// and viewport height.
func computeRenderParams(frames []tuiFrame, height int, heightMetricActive bool) renderViewParams {
- availableRows := height - 2 // toolbar + frame-status line
+ return computeRenderParamsForAvailableRows(frames, height-2, heightMetricActive)
+}
+
+// computeRenderParamsForAvailableRows derives row-layout parameters for a
+// frame set and pre-computed data-area row budget.
+func computeRenderParamsForAvailableRows(frames []tuiFrame, availableRows int, heightMetricActive bool) renderViewParams {
+ if availableRows < 1 {
+ availableRows = 1
+ }
maxRow := maxFrameRowForSet(frames, nil)
totalDepthRows := maxRow + 1
barHeight := computeBarHeight(availableRows, totalDepthRows, maxBarVisualHeight)
@@ -304,7 +313,91 @@ func computeRenderParams(frames []tuiFrame, height int, heightMetricActive bool)
availableRows: availableRows,
visibleFrames: countVisibleFrames(frames, nil),
truncated: truncated,
+ heightMetric: heightMetricActive,
+ }
+}
+
+// frameIndexAt returns the index of the frame rendered at terminal coordinates
+// (x, y), or -1 if no frame occupies that cell. showHelp adds one extra line
+// to the UI chrome so the frame area row calculations account for it.
+func frameIndexAt(frames []tuiFrame, x, y, width, height int, showHelp, heightMetricActive bool) int {
+ if len(frames) == 0 || width <= 0 || height <= 0 {
+ return -1
+ }
+ if x < 0 || x >= width || y < 0 {
+ return -1
+ }
+ extraLines := 1 // selection status line
+ if showHelp {
+ extraLines++
+ }
+ renderHeight := height - extraLines
+ if renderHeight < 3 {
+ renderHeight = 3
+ }
+ params := computeRenderParamsForAvailableRows(frames, renderHeight-2, heightMetricActive)
+ if y < 1 || y > params.availableRows {
+ return -1
+ }
+ targetRow := frameCoordToTargetRow(y-1, params)
+ if targetRow < 0 {
+ return -1
+ }
+ return findFrameAtRow(frames, targetRow, x, width)
+}
+
+// frameCoordToTargetRow converts a data-area row offset (0-based, after
+// stripping the toolbar row) into the logical frame row index. Returns -1 when
+// the coordinate falls in the top padding above the first visible row.
+func frameCoordToTargetRow(dataRow int, params renderViewParams) int {
+ if params.visibleFrames == 0 || params.availableRows < 1 || dataRow < 0 || dataRow >= params.availableRows {
+ return -1
+ }
+ renderedRows := (params.maxRow - params.rowOffset + 1) * params.barHeight
+ if params.heightMetric {
+ renderedRows = params.leafBarHeight + max(0, params.maxRow-params.rowOffset)*params.barHeight
+ }
+ padTop := 0
+ if renderedRows < params.availableRows {
+ padTop = params.availableRows - renderedRows
+ }
+ if dataRow < padTop {
+ return -1
+ }
+ rowInRender := dataRow - padTop
+ for row := params.maxRow; row >= params.rowOffset; row-- {
+ rowHeight := params.barHeight
+ if params.heightMetric && row == params.maxRow {
+ rowHeight = params.leafBarHeight
+ }
+ if rowInRender < rowHeight {
+ return row
+ }
+ rowInRender -= rowHeight
+ }
+ return -1
+}
+
+// findFrameAtRow scans frames for the narrowest one that occupies logical row
+// targetRow and contains pixel column x within [0, width). Returning the
+// narrowest frame resolves overlap between wide parent and narrow child bars.
+func findFrameAtRow(frames []tuiFrame, targetRow, x, width int) int {
+ best := -1
+ bestWidth := int(^uint(0) >> 1)
+ for idx, frame := range frames {
+ if frame.Row != targetRow || frame.Col >= width {
+ continue
+ }
+ right := min(width, frame.Col+frame.Width)
+ if x < frame.Col || x >= right {
+ continue
+ }
+ if frame.Width < bestWidth {
+ best = idx
+ bestWidth = frame.Width
+ }
}
+ return best
}
// buildToolbar assembles the top-of-view toolbar string and pads/trims it to