summaryrefslogtreecommitdiff
path: root/internal/tui/flamegraph/model.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-06 14:44:34 +0200
committerPaul Buetow <paul@buetow.org>2026-03-06 14:44:34 +0200
commit479f399aae8d3b28d9714214ea624d4a8cc0e886 (patch)
tree0609eee6378d170b3e6a4601560f58e98bf09cc8 /internal/tui/flamegraph/model.go
parent3e08a3d199fdf603b7c0a4002ca9822b6ecf2575 (diff)
flamegraph: keep non-matches visible in filter and add pgup/pgdn selection jumps
Diffstat (limited to 'internal/tui/flamegraph/model.go')
-rw-r--r--internal/tui/flamegraph/model.go105
1 files changed, 104 insertions, 1 deletions
diff --git a/internal/tui/flamegraph/model.go b/internal/tui/flamegraph/model.go
index 07bae5d..2f40a30 100644
--- a/internal/tui/flamegraph/model.go
+++ b/internal/tui/flamegraph/model.go
@@ -38,6 +38,8 @@ type flameKeyMap struct {
MoveDeeper key.Binding
PrevSibling key.Binding
NextSibling key.Binding
+ JumpTop key.Binding
+ JumpRoot key.Binding
ZoomIn key.Binding
ZoomUndo key.Binding
ZoomReset key.Binding
@@ -49,6 +51,8 @@ func defaultFlameKeyMap() flameKeyMap {
MoveDeeper: key.NewBinding(key.WithKeys("k", "up")),
PrevSibling: key.NewBinding(key.WithKeys("h", "left")),
NextSibling: key.NewBinding(key.WithKeys("l", "right")),
+ JumpTop: key.NewBinding(key.WithKeys("pgup", "pageup")),
+ JumpRoot: key.NewBinding(key.WithKeys("pgdown", "pgdn", "pagedown")),
ZoomIn: key.NewBinding(key.WithKeys("enter")),
ZoomUndo: key.NewBinding(key.WithKeys("backspace", "u", "esc")),
ZoomReset: key.NewBinding(),
@@ -233,6 +237,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case isNextSiblingKey(msg, m.keys):
handled = true
m.moveSibling(1)
+ case isJumpTopKey(msg, m.keys):
+ handled = true
+ m.jumpToTop()
+ case isJumpRootKey(msg, m.keys):
+ handled = true
+ m.jumpToRoot()
}
if m.selectedIdx != prev {
m.subtreeSet = computeSubtreeSetInto(m.frames, m.selectedIdx, m.subtreeSet)
@@ -263,7 +273,9 @@ func (m Model) ConsumesKey(msg tea.KeyPressMsg) bool {
isMoveShallowerKey(msg, m.keys),
isMoveDeeperKey(msg, m.keys),
isPrevSiblingKey(msg, m.keys),
- isNextSiblingKey(msg, m.keys):
+ isNextSiblingKey(msg, m.keys),
+ isJumpTopKey(msg, m.keys),
+ isJumpRootKey(msg, m.keys):
return true
default:
return false
@@ -582,6 +594,87 @@ func (m *Model) moveSibling(delta int) {
}
}
+func (m *Model) jumpToTop() {
+ if len(m.frames) == 0 {
+ return
+ }
+ m.clampSelection()
+ m.ensureSelectionNavigable()
+
+ include := m.navigableFrameSet()
+ currentCol := m.frames[m.selectedIdx].Col
+ bestIdx := -1
+ bestDepth := -1
+ bestDist := int(^uint(0) >> 1)
+
+ for idx, frame := range m.frames {
+ if include != nil && !include[idx] {
+ continue
+ }
+ dist := abs(frame.Col - currentCol)
+ if frame.Depth > bestDepth {
+ bestDepth = frame.Depth
+ bestIdx = idx
+ bestDist = dist
+ continue
+ }
+ if frame.Depth == bestDepth {
+ if dist < bestDist || (dist == bestDist && frame.Col < m.frames[bestIdx].Col) {
+ bestIdx = idx
+ bestDist = dist
+ }
+ }
+ }
+ if bestIdx >= 0 {
+ m.selectedIdx = bestIdx
+ }
+}
+
+func (m *Model) jumpToRoot() {
+ if len(m.frames) == 0 {
+ return
+ }
+ m.clampSelection()
+ m.ensureSelectionNavigable()
+
+ rootPath := m.currentRootPath()
+ if rootPath != "" {
+ if idx := m.frameIndexByPath(rootPath); idx >= 0 {
+ if !m.filterActive() || m.frameNavigable(idx) {
+ m.selectedIdx = idx
+ return
+ }
+ }
+ }
+
+ include := m.navigableFrameSet()
+ currentCol := m.frames[m.selectedIdx].Col
+ bestIdx := -1
+ bestDepth := int(^uint(0) >> 1)
+ bestDist := int(^uint(0) >> 1)
+ for idx, frame := range m.frames {
+ if include != nil && !include[idx] {
+ continue
+ }
+ dist := abs(frame.Col - currentCol)
+ if frame.Depth < bestDepth {
+ bestDepth = frame.Depth
+ bestDist = dist
+ bestIdx = idx
+ continue
+ }
+ if frame.Depth == bestDepth {
+ if dist < bestDist || (dist == bestDist && frame.Col < m.frames[bestIdx].Col) {
+ bestDist = dist
+ bestIdx = idx
+ }
+ }
+ }
+ if bestIdx >= 0 {
+ m.selectedIdx = bestIdx
+ }
+}
+
func framesAtDepth(frames []tuiFrame, depth int) []int {
return framesAtDepthFiltered(frames, depth, nil)
}
@@ -813,6 +906,16 @@ func isNextSiblingKey(msg tea.KeyPressMsg, keys flameKeyMap) bool {
return key.Matches(msg, keys.NextSibling) || msg.Code == tea.KeyRight || keyMatchesDirection(k, "right", 'C')
}
+func isJumpTopKey(msg tea.KeyPressMsg, keys flameKeyMap) bool {
+ k := strings.ToLower(keyString(msg))
+ return key.Matches(msg, keys.JumpTop) || msg.Code == tea.KeyPgUp || k == "pgup" || k == "pageup"
+}
+
+func isJumpRootKey(msg tea.KeyPressMsg, keys flameKeyMap) bool {
+ k := strings.ToLower(keyString(msg))
+ return key.Matches(msg, keys.JumpRoot) || msg.Code == tea.KeyPgDown || k == "pgdown" || k == "pgdn" || k == "pagedown"
+}
+
func keyMatchesDirection(keyName, plain string, ansiFinal byte) bool {
if keyName == plain || strings.HasSuffix(keyName, "+"+plain) {
return true