diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-06 13:36:51 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-06 13:36:51 +0200 |
| commit | ef12ce837176bd21deb455eb50a6c839af02b510 (patch) | |
| tree | c262ceeda0b419236a4b0b1826df8eb5e418b852 /internal/tui/tui.go | |
| parent | 10c5d48413afaef88626419d8c4bf9fbf6f1c902 (diff) | |
Add live flamegraph test modes and dynamic synthetic live feed
Diffstat (limited to 'internal/tui/tui.go')
| -rw-r--r-- | internal/tui/tui.go | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 7918c0f..0381784 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -166,6 +166,16 @@ func RunWithTraceStarter(starter TraceStarter) error { return err } +// RunTestFlamesWithTraceStarter starts the TUI directly on dashboard/flame view +// with a synthetic static flamegraph source. +func RunTestFlamesWithTraceStarter(starter TraceStarter) error { + cfg := flags.Get() + model := newModelWithRuntimeConfig(1, 1, cfg.TUIExportEnable, starter) + program := tea.NewProgram(model) + _, err := program.Run() + return err +} + // Model is the top-level Bubble Tea model that routes between PID picker and dashboard. type Model struct { screen Screen @@ -195,6 +205,15 @@ type Model struct { keyboardEnhancements tea.KeyboardEnhancementsMsg keyboardEnhancementsKnown bool + + lastKeyEventID string + lastKeyEventAt time.Time + lastKeyEventWasPress bool + // Some terminals emit release+press for a single physical key event. + // When we fallback-handle a release as a press, suppress the immediate + // matching press to avoid double-handling. + suppressPressKeyID string + suppressPressUntil time.Time } // NewModel creates the top-level TUI model. @@ -269,6 +288,12 @@ func initialWindowSizeCmd() tea.Cmd { // Update routes messages, transitions screens, and manages tracing startup state. func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + normalizedMsg, ok := m.normalizeKeyEvent(msg) + if !ok { + return m, nil + } + msg = normalizedMsg + switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = msg.Width @@ -382,6 +407,87 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.updateActiveModel(msg) } +func (m *Model) normalizeKeyEvent(msg tea.Msg) (tea.Msg, bool) { + switch keyMsg := msg.(type) { + case tea.KeyPressMsg: + keyID := keyEventID(keyMsg) + if m.shouldSuppressPress(keyID) { + return nil, false + } + m.recordKeyEvent(keyMsg, true) + return keyMsg, true + case tea.KeyReleaseMsg: + pressMsg := tea.KeyPressMsg(keyMsg) + keyID := keyEventID(pressMsg) + if m.lastKeyEventWasPress && keyID != "" && keyID == m.lastKeyEventID && time.Since(m.lastKeyEventAt) <= 500*time.Millisecond { + // Some terminals emit both press+release; avoid handling release as a duplicate. + m.lastKeyEventWasPress = false + return nil, false + } + if !releaseHasIdentity(pressMsg) { + // Ignore release messages that don't carry enough identity information. + // Some terminals emit these before a usable press event. + return nil, false + } + // Fallback: treat release as press for terminals that only emit release events. + m.armPressSuppression(keyID) + m.recordKeyEvent(pressMsg, false) + return pressMsg, true + default: + return msg, true + } +} + +func (m *Model) shouldSuppressPress(keyID string) bool { + if m.suppressPressKeyID == "" { + return false + } + if time.Now().After(m.suppressPressUntil) { + m.clearPressSuppression() + return false + } + if keyID == "" || keyID != m.suppressPressKeyID { + return false + } + m.clearPressSuppression() + return true +} + +func (m *Model) armPressSuppression(keyID string) { + if keyID == "" { + return + } + // Keep this short so fast repeated key presses still work naturally. + m.suppressPressKeyID = keyID + m.suppressPressUntil = time.Now().Add(60 * time.Millisecond) +} + +func (m *Model) clearPressSuppression() { + m.suppressPressKeyID = "" + m.suppressPressUntil = time.Time{} +} + +func (m *Model) recordKeyEvent(msg tea.KeyPressMsg, wasPress bool) { + m.lastKeyEventID = keyEventID(msg) + m.lastKeyEventAt = time.Now() + m.lastKeyEventWasPress = wasPress +} + +func keyEventID(msg tea.KeyPressMsg) string { + return fmt.Sprintf("code:%d/mod:%d", msg.Code, msg.Mod) +} + +func releaseHasIdentity(msg tea.KeyPressMsg) bool { + if msg.Code != 0 { + return true + } + if msg.Text != "" { + return true + } + keyStr := msg.String() + return keyStr != "" && keyStr != "\x00" +} + func (m Model) updateActiveModel(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.screen { case ScreenPIDPicker: |
