summaryrefslogtreecommitdiff
path: root/internal/tui/flamegraph/model.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-06 14:21:30 +0200
committerPaul Buetow <paul@buetow.org>2026-03-06 14:21:30 +0200
commitaa4f638206b9b79de267f9a1daab7ec6698b241d (patch)
tree44c913b6be46460c184eac580d26a11973a6e283 /internal/tui/flamegraph/model.go
parentef12ce837176bd21deb455eb50a6c839af02b510 (diff)
Fix real live flamegraph key handling and startup viewport sync
Diffstat (limited to 'internal/tui/flamegraph/model.go')
-rw-r--r--internal/tui/flamegraph/model.go39
1 files changed, 35 insertions, 4 deletions
diff --git a/internal/tui/flamegraph/model.go b/internal/tui/flamegraph/model.go
index b205d33..66fefc9 100644
--- a/internal/tui/flamegraph/model.go
+++ b/internal/tui/flamegraph/model.go
@@ -87,8 +87,7 @@ type Model struct {
animation AnimationState
animating bool
paused bool
- // hasNavigableSnapshot flips once we have at least one selectable non-root
- // frame. Paused mode can still bootstrap snapshots until then.
+ // hasNavigableSnapshot flips once we have at least one selectable non-root frame.
hasNavigableSnapshot bool
isDark bool
keys flameKeyMap
@@ -308,8 +307,9 @@ func (m *Model) RefreshFromLiveTrie() bool {
if m.liveTrie == nil {
return false
}
- // Keep bootstrapping while paused until we have a navigable snapshot.
- if m.paused && m.snapshot != nil && m.hasNavigableSnapshot {
+ // Once a snapshot exists, paused mode must freeze it regardless of current
+ // navigability so selection and percentages remain stable.
+ if m.paused && m.snapshot != nil {
return false
}
version := m.liveTrie.Version()
@@ -371,6 +371,11 @@ func (m *Model) SetDarkMode(isDark bool) {
}
func (m *Model) rebuildFrames(animate bool) {
+ prevPath := ""
+ if len(m.frames) > 0 && m.selectedIdx >= 0 && m.selectedIdx < len(m.frames) {
+ prevPath = m.frames[m.selectedIdx].Path
+ }
+
var root *snapshotNode
rootPath := ""
if m.zoomRoot != nil {
@@ -391,6 +396,7 @@ func (m *Model) rebuildFrames(animate bool) {
if len(m.frames) > 1 {
m.hasNavigableSnapshot = true
}
+ m.restoreSelectionByPath(prevPath)
m.clampSelection()
m.recomputeFilterState()
m.ensureSelectionNavigable()
@@ -398,6 +404,31 @@ func (m *Model) rebuildFrames(animate bool) {
m.subtreeSet = computeSubtreeSetInto(m.frames, m.selectedIdx, m.subtreeSet)
}
+func (m *Model) restoreSelectionByPath(path string) {
+ if path == "" || len(m.frames) == 0 {
+ return
+ }
+ if idx := m.frameIndexByPath(path); idx >= 0 {
+ m.selectedIdx = idx
+ return
+ }
+ for idx, frame := range m.frames {
+ if hasPathBoundaryPrefix(path, frame.Path) || hasPathBoundaryPrefix(frame.Path, path) {
+ m.selectedIdx = idx
+ return
+ }
+ }
+}
+
+func (m Model) frameIndexByPath(path string) int {
+ for idx, frame := range m.frames {
+ if frame.Path == path {
+ return idx
+ }
+ }
+ return -1
+}
+
func (m *Model) zoomIn() {
if len(m.frames) == 0 || m.snapshot == nil {
m.statusMessage = "Zoom unavailable: no frame selected"