Skip to content

Commit

Permalink
fix: bugs with inactivity detection
Browse files Browse the repository at this point in the history
  • Loading branch information
cfoust committed Oct 26, 2023
1 parent a9bc2f9 commit 0326883
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 90 deletions.
5 changes: 4 additions & 1 deletion pkg/mux/screen/replay/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ type PlaybackRateEvent struct {
Rate int
}

const PLAYBACK_FPS = 30
const (
PLAYBACK_FPS = 30
IDLE_THRESHOLD = time.Second
)

type PlaybackEvent struct {
Since time.Time
Expand Down
167 changes: 97 additions & 70 deletions pkg/mux/screen/replay/replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package replay

import (
"testing"
"time"

"github.com/cfoust/cy/pkg/bind"
"github.com/cfoust/cy/pkg/geom"
Expand All @@ -14,60 +15,64 @@ import (
"github.com/xo/terminfo"
)

func createTestSession() []sessions.Event {
s := sessions.NewSimulator()
s.Add(
"\033[20h", // CRLF -- why is this everywhere?
geom.DEFAULT_SIZE,
"test string please ignore",
)
s.Term(terminfo.ClearScreen)
s.Add("take two")
s.Term(terminfo.ClearScreen)
s.Add("test")
var sim = sessions.NewSimulator

return s.Events()
func createTestSession() []sessions.Event {
return sim().
Add(
"\033[20h", // CRLF -- why is this everywhere?
geom.DEFAULT_SIZE,
"test string please ignore",
).
Term(terminfo.ClearScreen).
Add("take two").
Term(terminfo.ClearScreen).
Add("test").
Events()
}

func input(m taro.Model, msgs ...interface{}) taro.Model {
var cmd tea.Cmd
var realMsg tea.Msg
for _, msg := range msgs {
realMsg = msg
switch msg := msg.(type) {
case ActionType:
realMsg = ActionEvent{Type: msg}
case geom.Size:
realMsg = tea.WindowSizeMsg{
Width: msg.C,
Height: msg.R,
func createTest(events []sessions.Event) (*Replay, func(msgs ...interface{})) {
var r = newReplay(events, bind.NewEngine[bind.Action]())
var m taro.Model = r

return r, func(msgs ...interface{}) {
var cmd tea.Cmd
var realMsg tea.Msg
for _, msg := range msgs {
realMsg = msg
switch msg := msg.(type) {
case ActionType:
realMsg = ActionEvent{Type: msg}
case geom.Size:
realMsg = tea.WindowSizeMsg{
Width: msg.C,
Height: msg.R,
}
case string:
keyMsgs := taro.KeysToMsg(msg)
if len(keyMsgs) == 1 {
realMsg = keyMsgs[0]
}
}
case string:
keyMsgs := taro.KeysToMsg(msg)
if len(keyMsgs) == 1 {
realMsg = keyMsgs[0]
}
}

m, cmd = m.Update(realMsg)
m.View(tty.New(geom.DEFAULT_SIZE))
for cmd != nil {
m, cmd = m.Update(cmd())
m, cmd = m.Update(realMsg)
m.View(tty.New(geom.DEFAULT_SIZE))
for cmd != nil {
m, cmd = m.Update(cmd())
m.View(tty.New(geom.DEFAULT_SIZE))
}
}
}

return m
}

func TestSearch(t *testing.T) {
var r = newReplay(createTestSession(), bind.NewEngine[bind.Action]())
input(r, ActionBeginning, ActionSearchForward, "test", "enter")
r, i := createTest(createTestSession())
i(ActionBeginning, ActionSearchForward, "test", "enter")
require.Equal(t, 2, len(r.matches))
}

func TestIndex(t *testing.T) {
var r = newReplay(createTestSession(), bind.NewEngine[bind.Action]())
r, _ := createTest(createTestSession())
r.gotoIndex(2, 0)
require.Equal(t, "t ", r.getLine(0).String()[:2])
r.gotoIndex(2, 1)
Expand All @@ -81,13 +86,13 @@ func TestIndex(t *testing.T) {
}

func TestViewport(t *testing.T) {
s := sessions.NewSimulator()
s.Add(geom.Size{R: 20, C: 20})
s.Term(terminfo.ClearScreen)
s.Term(terminfo.CursorAddress, 19, 19)
s := sim().
Add(geom.Size{R: 20, C: 20}).
Term(terminfo.ClearScreen).
Term(terminfo.CursorAddress, 19, 19)

var r = newReplay(s.Events(), bind.NewEngine[bind.Action]())
input(r, geom.Size{R: 10, C: 10})
r, i := createTest(s.Events())
i(geom.Size{R: 10, C: 10})
require.Equal(t, geom.Vec2{R: 0, C: 0}, r.minOffset)
require.Equal(t, geom.Vec2{R: 11, C: 10}, r.maxOffset)
require.Equal(t, geom.Vec2{R: 11, C: 10}, r.offset)
Expand All @@ -107,42 +112,42 @@ func TestScroll(t *testing.T) {
"seven",
)

var r = newReplay(s.Events(), bind.NewEngine[bind.Action]())
input(r, geom.Size{R: 3, C: 10})
r, i := createTest(s.Events())
i(geom.Size{R: 3, C: 10})
require.Equal(t, 1, r.cursor.R)
require.Equal(t, 5, r.cursor.C)
require.Equal(t, 5, r.desiredCol)
// six
// seven[ ]

input(r, ActionScrollUp)
i(ActionScrollUp)
// five
// si[x]
require.Equal(t, 2, r.cursor.C)
require.Equal(t, 5, r.desiredCol)

input(r, ActionScrollUp)
i(ActionScrollUp)
// four
// fiv[e]
require.Equal(t, 3, r.cursor.C)
require.Equal(t, 5, r.desiredCol)

input(r, ActionScrollDown)
i(ActionScrollDown)
// fiv[e]
// six
require.Equal(t, 0, r.cursor.R)
require.Equal(t, 3, r.cursor.C)

input(r, ActionScrollDown)
i(ActionScrollDown)
// si[x]
// seven
require.Equal(t, 0, r.cursor.R)
require.Equal(t, 2, r.cursor.C)

input(r, ActionBeginning)
i(ActionBeginning)
require.Equal(t, -2, r.viewportToTerm(r.cursor).R)

input(r, ActionEnd)
i(ActionEnd)
require.Equal(t, 4, r.viewportToTerm(r.cursor).R)
}

Expand All @@ -157,46 +162,68 @@ func TestCursor(t *testing.T) {
"foo ",
)

var r = newReplay(s.Events(), bind.NewEngine[bind.Action]())
input(r, geom.Size{R: 3, C: 10})
r, i := createTest(s.Events())
i(geom.Size{R: 3, C: 10})
require.Equal(t, 2, r.offset.R)
require.Equal(t, 1, r.cursor.R)
require.Equal(t, 4, r.cursor.C)
require.Equal(t, 4, r.desiredCol)
input(r, ActionCursorUp)
i(ActionCursorUp)
require.Equal(t, 4, r.cursor.C)
input(r, ActionCursorUp)
i(ActionCursorUp)
require.Equal(t, 5, r.cursor.C)
input(r, ActionCursorUp)
i(ActionCursorUp)
require.Equal(t, 2, r.cursor.C)
input(r, ActionCursorRight)
i(ActionCursorRight)
require.Equal(t, 2, r.cursor.C)
input(r, ActionCursorLeft, ActionCursorLeft, ActionCursorLeft, ActionCursorLeft)
i(ActionCursorLeft, ActionCursorLeft, ActionCursorLeft, ActionCursorLeft)
require.Equal(t, 0, r.cursor.C)
input(r, ActionCursorDown)
i(ActionCursorDown)
require.Equal(t, 5, r.cursor.C)
input(r, ActionCursorDown)
i(ActionCursorDown)
require.Equal(t, 0, r.cursor.C)

// at end of screen
input(r, ActionCursorDown)
i(ActionCursorDown)
require.Equal(t, 0, r.cursor.C)
require.Equal(t, 1, r.cursor.R)

// moving down past last occupied line should do nothing
input(r, ActionCursorDown)
i(ActionCursorDown)
require.Equal(t, geom.Vec2{
R: 3,
C: 0,
}, r.viewportToTerm(r.cursor))
}

func TestEmpty(t *testing.T) {
s := sessions.NewSimulator()
s.Add(
geom.Size{R: 5, C: 10},
)
s := sim().Add(geom.Size{R: 5, C: 10})
_, i := createTest(s.Events())
i(geom.Size{R: 3, C: 10}, ActionCursorDown)
// should not panic
}

var r = newReplay(s.Events(), bind.NewEngine[bind.Action]())
input(r, geom.Size{R: 3, C: 10}, ActionCursorDown)
func TestTime(t *testing.T) {
delta := time.Second / PLAYBACK_FPS
size := geom.Size{R: 5, C: 10}
e := sim().
Add(size).
AddTime(0, "test").
AddTime(IDLE_THRESHOLD*2, "test").
AddTime(time.Second, "test").
Events()

r, i := createTest(e)
i(size)
r.gotoIndex(0, -1)
require.Equal(t, e[0].Stamp, r.currentTime)
r.setTimeDelta(delta)
require.Equal(t, 1, r.location.Index)
r.setTimeDelta(delta)
require.Equal(t, 2, r.location.Index)
require.Equal(t, e[2].Stamp, r.currentTime)
r.setTimeDelta(-delta)
require.Equal(t, 1, r.location.Index)
r.setTimeDelta(-delta)
require.Equal(t, 0, r.location.Index)
}
40 changes: 33 additions & 7 deletions pkg/mux/screen/replay/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,14 @@ func (r *Replay) setTimeDelta(delta time.Duration) {
return
}

// We use setIndex after this because our timestamp can be anywhere
// within the valid range; gotoIndex sets the time to the timestamp of
// the event
// First, just check to see whether we've entered another event
currentIndex := r.location.Index
var nextIndex int = currentIndex
if newTime.Before(r.currentTime) {
indexStamp := r.events[r.location.Index].Stamp
for i := r.location.Index; i >= 0; i-- {
indexStamp := r.events[currentIndex].Stamp
for i := currentIndex; i >= 0; i-- {
if newTime.Before(indexStamp) && newTime.After(r.events[i].Stamp) {
r.setIndex(i, -1, false)
nextIndex = i
break
}
}
Expand All @@ -167,5 +167,31 @@ func (r *Replay) setTimeDelta(delta time.Duration) {
}
}

r.currentTime = newTime
// If this resulted in a change, we just jump to it immediately
if currentIndex != nextIndex {
r.currentTime = newTime
r.setIndex(nextIndex, -1, false)
return
}

// It didn't, which can only mean that we're waiting for the next event
var nextTime time.Time
if newTime.Before(r.currentTime) {
nextTime = r.events[currentIndex].Stamp
} else {
// we know `currentIndex` is not the last one because `end` is the time of the last event
nextTime = r.events[currentIndex+1].Stamp
}

if newTime.Sub(nextTime).Abs() < IDLE_THRESHOLD {
r.currentTime = newTime
return
}

if newTime.Before(r.currentTime) {
r.setIndex(currentIndex-1, -1, false)
} else {
r.setIndex(currentIndex+1, -1, false)
}
r.currentTime = nextTime
}
3 changes: 1 addition & 2 deletions pkg/mux/screen/replay/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,14 @@ func (r *Replay) View(state *tty.State) {
}
}

viewport := r.viewport
termCursor := r.termToViewport(r.getTerminalCursor())
if r.isSelectionMode {
state.Cursor.X = r.cursor.C
state.Cursor.Y = r.cursor.R

// In selection mode, leave behind a ghost cursor where the
// terminal's cursor is
if termCursor != r.cursor && termCursor.R >= 0 && termCursor.R < viewport.R && termCursor.C >= 0 && termCursor.C < viewport.C {
if r.isInViewport(termCursor) {
state.Image[termCursor.R][termCursor.C].BG = 8
}
} else {
Expand Down
Loading

0 comments on commit 0326883

Please sign in to comment.