summaryrefslogtreecommitdiffstats
path: root/vendor
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-04-05 08:52:09 +1000
committerJesse Duffield <jessedduffield@gmail.com>2021-04-06 19:34:32 +1000
commit6a0066253f648d23966fd97b93cf0453a26be2ec (patch)
treee727f558c298f7799a96114434126cbe84abb633 /vendor
parentd627b3bfc829f4ccaad910bf6136eda8af183740 (diff)
move recording code into gocui
Diffstat (limited to 'vendor')
-rw-r--r--vendor/github.com/jesseduffield/gocui/gui.go65
-rw-r--r--vendor/github.com/jesseduffield/gocui/recording.go52
-rw-r--r--vendor/github.com/jesseduffield/gocui/tcell_driver.go43
3 files changed, 137 insertions, 23 deletions
diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go
index a15e5b71c..19ea5b2d6 100644
--- a/vendor/github.com/jesseduffield/gocui/gui.go
+++ b/vendor/github.com/jesseduffield/gocui/gui.go
@@ -75,13 +75,36 @@ type GuiMutexes struct {
ViewsMutex sync.Mutex
}
+type PlayMode int
+
+const (
+ NORMAL PlayMode = iota
+ RECORDING
+ REPLAYING
+)
+
+type Recording struct {
+ KeyEvents []*TcellKeyEventWrapper
+}
+
+type replayedEvents struct {
+ keys chan *TcellKeyEventWrapper
+}
+
+type RecordingConfig struct {
+ Speed int
+ Leeway int
+}
+
// Gui represents the whole User Interface, including the views, layouts
// and keybindings.
type Gui struct {
- // ReplayedEvents is a channel for passing pre-recorded input events, for the purposes of testing
- ReplayedEvents chan GocuiEvent
- RecordEvents bool
- RecordedEvents chan *GocuiEvent
+ RecordingConfig
+ Recording *Recording
+ // ReplayedEvents is for passing pre-recorded input events, for the purposes of testing
+ ReplayedEvents replayedEvents
+ PlayMode PlayMode
+ StartTime time.Time
tabClickBindings []*tabClickBinding
gEvents chan GocuiEvent
@@ -135,7 +158,7 @@ type Gui struct {
}
// NewGui returns a new Gui object with a given output mode.
-func NewGui(mode OutputMode, supportOverlaps bool, recordEvents bool) (*Gui, error) {
+func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode) (*Gui, error) {
err := tcellInit()
if err != nil {
return nil, err
@@ -147,10 +170,18 @@ func NewGui(mode OutputMode, supportOverlaps bool, recordEvents bool) (*Gui, err
g.stop = make(chan struct{})
- g.ReplayedEvents = make(chan GocuiEvent)
g.gEvents = make(chan GocuiEvent, 20)
g.userEvents = make(chan userEvent, 20)
- g.RecordedEvents = make(chan *GocuiEvent)
+
+ if playMode == RECORDING {
+ g.Recording = &Recording{
+ KeyEvents: []*TcellKeyEventWrapper{},
+ }
+ } else if playMode == REPLAYING {
+ g.ReplayedEvents = replayedEvents{
+ keys: make(chan *TcellKeyEventWrapper),
+ }
+ }
if runtime.GOOS != "windows" {
g.maxX, g.maxY, err = g.getTermWindowSize()
@@ -173,7 +204,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, recordEvents bool) (*Gui, err
g.NextSearchMatchKey = 'n'
g.PrevSearchMatchKey = 'N'
- g.RecordEvents = recordEvents
+ g.PlayMode = playMode
return g, nil
}
@@ -533,13 +564,17 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
// MainLoop runs the main loop until an error is returned. A successful
// finish should return ErrQuit.
func (g *Gui) MainLoop() error {
+ if g.PlayMode == REPLAYING {
+ g.replayRecording()
+ }
+
go func() {
for {
select {
case <-g.stop:
return
default:
- g.gEvents <- pollEvent()
+ g.gEvents <- g.pollEvent()
}
}
}()
@@ -554,10 +589,6 @@ func (g *Gui) MainLoop() error {
if err := g.handleEvent(&ev); err != nil {
return err
}
- case ev := <-g.ReplayedEvents:
- if err := g.handleEvent(&ev); err != nil {
- return err
- }
case ev := <-g.userEvents:
if err := ev.f(g); err != nil {
return err
@@ -580,10 +611,6 @@ func (g *Gui) consumeevents() error {
if err := g.handleEvent(&ev); err != nil {
return err
}
- case ev := <-g.ReplayedEvents:
- if err := g.handleEvent(&ev); err != nil {
- return err
- }
case ev := <-g.userEvents:
if err := ev.f(g); err != nil {
return err
@@ -597,10 +624,6 @@ func (g *Gui) consumeevents() error {
// handleEvent handles an event, based on its type (key-press, error,
// etc.)
func (g *Gui) handleEvent(ev *GocuiEvent) error {
- if g.RecordEvents {
- g.RecordedEvents <- ev
- }
-
switch ev.Type {
case eventKey, eventMouse:
return g.onKey(ev)
diff --git a/vendor/github.com/jesseduffield/gocui/recording.go b/vendor/github.com/jesseduffield/gocui/recording.go
new file mode 100644
index 000000000..2c3241b94
--- /dev/null
+++ b/vendor/github.com/jesseduffield/gocui/recording.go
@@ -0,0 +1,52 @@
+package gocui
+
+import (
+ "log"
+ "time"
+)
+
+func (g *Gui) replayRecording() {
+ ticker := time.NewTicker(time.Millisecond)
+ defer ticker.Stop()
+
+ // The playback could be paused at any time because integration tests run concurrently.
+ // Therefore we can't just check for a given event whether we've passed its timestamp,
+ // or else we'll have an explosion of keypresses after the test is resumed.
+ // We need to check if we've waited long enough since the last event was replayed.
+ // Only handling key events for now.
+ for i, event := range g.Recording.KeyEvents {
+ var prevEventTimestamp int64 = 0
+ if i > 0 {
+ prevEventTimestamp = g.Recording.KeyEvents[i-1].Timestamp
+ }
+ timeToWait := (event.Timestamp - prevEventTimestamp) / int64(g.RecordingConfig.Speed)
+ if i == 0 {
+ timeToWait += int64(g.RecordingConfig.Leeway)
+ }
+ var timeWaited int64 = 0
+ middle:
+ for {
+ select {
+ case <-ticker.C:
+ timeWaited += 1
+ if g != nil && timeWaited >= timeToWait {
+ g.ReplayedEvents.keys <- event
+ break middle
+ }
+ case <-g.stop:
+ return
+ }
+ }
+ }
+
+ // leaving some time for any handlers to execute before quitting
+ time.Sleep(time.Second * 1)
+
+ g.Update(func(*Gui) error {
+ return ErrQuit
+ })
+
+ time.Sleep(time.Second * 1)
+
+ log.Fatal("gocui should have already exited")
+}
diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go
index 517f102dd..637d650c5 100644
--- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go
+++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go
@@ -6,6 +6,7 @@ package gocui
import (
"sync"
+ "time"
"github.com/gdamore/tcell/v2"
)
@@ -144,9 +145,43 @@ var (
lastY int = 0
)
+// this wrapper struct has public keys so we can easily serialize/deserialize to JSON
+type TcellKeyEventWrapper struct {
+ Timestamp int64
+ Mod tcell.ModMask
+ Key tcell.Key
+ Ch rune
+}
+
+func NewTcellKeyEventWrapper(event *tcell.EventKey, timestamp int64) *TcellKeyEventWrapper {
+ return &TcellKeyEventWrapper{
+ Timestamp: timestamp,
+ Mod: event.Modifiers(),
+ Key: event.Key(),
+ Ch: event.Rune(),
+ }
+}
+
+func (wrapper TcellKeyEventWrapper) toTcellEvent() tcell.Event {
+ return tcell.NewEventKey(wrapper.Key, wrapper.Ch, wrapper.Mod)
+}
+
+func (g *Gui) timeSinceStart() int64 {
+ return time.Since(g.StartTime).Nanoseconds() / 1e6
+}
+
// pollEvent get tcell.Event and transform it into gocuiEvent
-func pollEvent() GocuiEvent {
- tev := Screen.PollEvent()
+func (g *Gui) pollEvent() GocuiEvent {
+ var tev tcell.Event
+ if g.PlayMode == REPLAYING {
+ select {
+ case ev := <-g.ReplayedEvents.keys:
+ tev = (ev).toTcellEvent()
+ }
+ } else {
+ tev = Screen.PollEvent()
+ }
+
switch tev := tev.(type) {
case *tcell.EventInterrupt:
return GocuiEvent{Type: eventInterrupt}
@@ -154,6 +189,10 @@ func pollEvent() GocuiEvent {
w, h := tev.Size()
return GocuiEvent{Type: eventResize, Width: w, Height: h}
case *tcell.EventKey:
+ if g.PlayMode == RECORDING {
+ g.Recording.KeyEvents = append(g.Recording.KeyEvents, NewTcellKeyEventWrapper(tev, g.timeSinceStart()))
+ }
+
k := tev.Key()
ch := rune(0)
if k == tcell.KeyRune {