diff --git a/pkg/mux/screen/replay/module.go b/pkg/mux/screen/replay/module.go index c5a74b28..287df878 100644 --- a/pkg/mux/screen/replay/module.go +++ b/pkg/mux/screen/replay/module.go @@ -69,10 +69,6 @@ type Replay struct { var _ taro.Model = (*Replay)(nil) -func (r *Replay) quit() (taro.Model, tea.Cmd) { - return r, tea.Quit -} - func (r *Replay) getTerminalCursor() geom.Vec2 { cursor := r.terminal.Cursor() return geom.Vec2{ @@ -122,140 +118,6 @@ func (r *Replay) Init() tea.Cmd { return textinput.Blink } -func reverse[S ~[]E, E any](s S) { - for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { - s[i], s[j] = s[j], s[i] - } -} - -func (r *Replay) Update(msg tea.Msg) (taro.Model, tea.Cmd) { - viewport := r.viewport - - switch msg := msg.(type) { - case PlaybackRateEvent: - r.playbackRate = geom.Clamp(msg.Rate, -10, 10) - if r.playbackRate == 0 { - r.playbackRate = 1 - } - return r, nil - case PlaybackEvent: - if !r.isPlaying { - return r, nil - } - - r.setTimeDelta(time.Duration(int64(time.Now().Sub(msg.Since)) * int64(r.playbackRate))) - - return r.scheduleUpdate() - case tea.WindowSizeMsg: - return r.setViewport( - r.viewport, - geom.Size{ - R: msg.Height, - C: msg.Width, - }, - ) - case SearchResultEvent: - return r.handleSearchResult(msg) - } - - if r.isSearching { - return r.handleSearchInput(msg) - } - - // These events do not stop playback - switch msg := msg.(type) { - case ActionEvent: - switch msg.Type { - case ActionTimePlay: - r.isPlaying = !r.isPlaying - - if r.isPlaying { - r.exitSelectionMode() - return r.scheduleUpdate() - } - - return r, nil - } - case taro.KeyMsg: - // Pass unmatched keys into the binding engine; because of how - // text input works, :replay bindings have to be activated - // selectively - return r, func() tea.Msg { - r.binds.InputMessage(msg) - return nil - } - } - - // Every other event causes us to pause - r.isPlaying = false - - switch msg := msg.(type) { - case taro.MouseMsg: - switch msg.Type { - case taro.MouseWheelUp: - r.setScroll(r.offset.R - 1) - case taro.MouseWheelDown: - r.setScroll(r.offset.R + 1) - } - case ActionEvent: - switch msg.Type { - case ActionQuit: - if r.isSelectionMode { - r.exitSelectionMode() - return r, nil - } - - return r.quit() - case ActionBeginning: - if r.isSelectionMode { - r.moveCursorDelta( - -r.viewportToTerm(r.cursor).R+r.minOffset.R, - 0, - ) - } else { - r.gotoIndex(0, -1) - } - case ActionEnd: - if r.isSelectionMode { - r.moveCursorDelta( - (r.getTerminalSize().R-1)-r.viewportToTerm(r.cursor).R, - 0, - ) - } else { - r.gotoIndex(-1, -1) - } - case ActionSearchAgain, ActionSearchReverse: - r.searchAgain(msg.Type != ActionSearchReverse) - case ActionSearchForward, ActionSearchBackward: - r.isSearching = true - r.isForward = msg.Type == ActionSearchForward - r.searchInput.Reset() - case ActionTimeStepBack: - r.gotoIndex(r.location.Index-1, -1) - case ActionTimeStepForward: - r.gotoIndex(r.location.Index+1, -1) - case ActionScrollUpHalf: - r.moveCursorDelta(-(viewport.R / 2), 0) - case ActionScrollDownHalf: - r.moveCursorDelta((viewport.R / 2), 0) - case ActionScrollUp: - r.setScroll(r.offset.R - 1) - case ActionScrollDown: - r.setScroll(r.offset.R + 1) - case ActionCursorDown: - r.moveCursorDelta(1, 0) - case ActionCursorUp: - r.moveCursorDelta(-1, 0) - case ActionCursorLeft: - r.moveCursorDelta(0, -1) - case ActionCursorRight: - r.moveCursorDelta(0, 1) - } - } - - return r, nil -} - func newReplay( events []sessions.Event, binds *bind.Engine[bind.Action], diff --git a/pkg/mux/screen/replay/search.go b/pkg/mux/screen/replay/search.go index 08836f7f..9291ffb9 100644 --- a/pkg/mux/screen/replay/search.go +++ b/pkg/mux/screen/replay/search.go @@ -9,6 +9,12 @@ import ( tea "github.com/charmbracelet/bubbletea" ) +func reverse[S ~[]E, E any](s S) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} + func (r *Replay) gotoMatch(index int) { if len(r.matches) == 0 { return diff --git a/pkg/mux/screen/replay/update.go b/pkg/mux/screen/replay/update.go new file mode 100644 index 00000000..6e6f2b40 --- /dev/null +++ b/pkg/mux/screen/replay/update.go @@ -0,0 +1,142 @@ +package replay + +import ( + "time" + + "github.com/cfoust/cy/pkg/geom" + "github.com/cfoust/cy/pkg/taro" + + tea "github.com/charmbracelet/bubbletea" +) + +func (r *Replay) quit() (taro.Model, tea.Cmd) { + return r, tea.Quit +} + +func (r *Replay) Update(msg tea.Msg) (taro.Model, tea.Cmd) { + viewport := r.viewport + + switch msg := msg.(type) { + case PlaybackRateEvent: + r.playbackRate = geom.Clamp(msg.Rate, -10, 10) + if r.playbackRate == 0 { + r.playbackRate = 1 + } + return r, nil + case PlaybackEvent: + if !r.isPlaying { + return r, nil + } + + r.setTimeDelta(time.Duration(int64(time.Now().Sub(msg.Since)) * int64(r.playbackRate))) + + return r.scheduleUpdate() + case tea.WindowSizeMsg: + return r.setViewport( + r.viewport, + geom.Size{ + R: msg.Height, + C: msg.Width, + }, + ) + case SearchResultEvent: + return r.handleSearchResult(msg) + } + + if r.isSearching { + return r.handleSearchInput(msg) + } + + // These events do not stop playback + switch msg := msg.(type) { + case ActionEvent: + switch msg.Type { + case ActionTimePlay: + r.isPlaying = !r.isPlaying + + if r.isPlaying { + r.exitSelectionMode() + return r.scheduleUpdate() + } + + return r, nil + } + case taro.KeyMsg: + // Pass unmatched keys into the binding engine; because of how + // text input works, :replay bindings have to be activated + // selectively + return r, func() tea.Msg { + r.binds.InputMessage(msg) + return nil + } + } + + // Every other event causes us to pause + r.isPlaying = false + + switch msg := msg.(type) { + case taro.MouseMsg: + switch msg.Type { + case taro.MouseWheelUp: + r.setScroll(r.offset.R - 1) + case taro.MouseWheelDown: + r.setScroll(r.offset.R + 1) + } + case ActionEvent: + switch msg.Type { + case ActionQuit: + if r.isSelectionMode { + r.exitSelectionMode() + return r, nil + } + + return r.quit() + case ActionBeginning: + if r.isSelectionMode { + r.moveCursorDelta( + -r.viewportToTerm(r.cursor).R+r.minOffset.R, + 0, + ) + } else { + r.gotoIndex(0, -1) + } + case ActionEnd: + if r.isSelectionMode { + r.moveCursorDelta( + (r.getTerminalSize().R-1)-r.viewportToTerm(r.cursor).R, + 0, + ) + } else { + r.gotoIndex(-1, -1) + } + case ActionSearchAgain, ActionSearchReverse: + r.searchAgain(msg.Type != ActionSearchReverse) + case ActionSearchForward, ActionSearchBackward: + r.isSearching = true + r.isForward = msg.Type == ActionSearchForward + r.searchInput.Reset() + case ActionTimeStepBack: + r.gotoIndex(r.location.Index-1, -1) + case ActionTimeStepForward: + r.gotoIndex(r.location.Index+1, -1) + case ActionScrollUpHalf: + r.moveCursorDelta(-(viewport.R / 2), 0) + case ActionScrollDownHalf: + r.moveCursorDelta((viewport.R / 2), 0) + case ActionScrollUp: + r.setScroll(r.offset.R - 1) + case ActionScrollDown: + r.setScroll(r.offset.R + 1) + case ActionCursorDown: + r.moveCursorDelta(1, 0) + case ActionCursorUp: + r.moveCursorDelta(-1, 0) + case ActionCursorLeft: + r.moveCursorDelta(0, -1) + case ActionCursorRight: + r.moveCursorDelta(0, 1) + } + } + + return r, nil +}