package gui import ( "encoding/json" "io/ioutil" "log" "os" "strconv" "time" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/utils" ) func recordingEvents() bool { return recordEventsTo() != "" } func recordEventsTo() string { return os.Getenv("RECORD_EVENTS_TO") } func (gui *Gui) timeSinceStart() int64 { return time.Since(gui.StartTime).Nanoseconds() / 1e6 } func (gui *Gui) replayRecordedEvents() { if os.Getenv("REPLAY_EVENTS_FROM") == "" { return } go utils.Safe(func() { time.Sleep(time.Second * 20) log.Fatal("20 seconds is up, lazygit recording took too long to complete") }) events, err := gui.loadRecordedEvents() if err != nil { log.Fatal(err) } ticker := time.NewTicker(time.Millisecond) defer ticker.Stop() // might need to add leeway if this ends up flakey var leeway int64 = 0 // humans are slow so this speeds things up. speed := 1 envReplaySpeed := os.Getenv("REPLAY_SPEED") if envReplaySpeed != "" { var err error speed, err = strconv.Atoi(envReplaySpeed) if err != nil { log.Fatal(err) } } // 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. for i, event := range events { var prevEventTimestamp int64 = 0 if i > 0 { prevEventTimestamp = events[i-1].Timestamp } timeToWait := (event.Timestamp - prevEventTimestamp) / int64(speed) if i == 0 { timeToWait += leeway } var timeWaited int64 = 0 middle: for { select { case <-ticker.C: timeWaited += 1 if gui.g != nil && timeWaited >= timeToWait { gui.g.ReplayedEvents <- *event.Event break middle } case <-gui.stopChan: return } } } time.Sleep(time.Second * 1) gui.g.Update(func(*gocui.Gui) error { return gocui.ErrQuit }) time.Sleep(time.Second * 1) log.Fatal("lazygit should have already exited") } func (gui *Gui) loadRecordedEvents() ([]RecordedEvent, error) { path := os.Getenv("REPLAY_EVENTS_FROM") data, err := ioutil.ReadFile(path) if err != nil { return nil, err } events := []RecordedEvent{} err = json.Unmarshal(data, &events) if err != nil { return nil, err } return events, nil } func (gui *Gui) saveRecordedEvents() error { if !recordingEvents() { return nil } jsonEvents, err := json.Marshal(gui.RecordedEvents) if err != nil { return err } path := recordEventsTo() return ioutil.WriteFile(path, jsonEvents, 0600) } func (gui *Gui) recordEvents() { for event := range gui.g.RecordedEvents { recordedEvent := RecordedEvent{ Timestamp: gui.timeSinceStart(), Event: event, } gui.RecordedEvents = append(gui.RecordedEvents, recordedEvent) } }