You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
4.8 KiB
220 lines
4.8 KiB
package main |
|
|
|
import ( |
|
"flag" |
|
"fmt" |
|
"log" |
|
"net" |
|
"path" |
|
"strings" |
|
"time" |
|
|
|
"github.com/gdamore/tcell/v2" |
|
"gitlab.com/tslocum/cbind" |
|
"gitlab.com/tslocum/cview" |
|
) |
|
|
|
var ( |
|
app *cview.Application |
|
|
|
editorPaused bool |
|
statusBuffer *cview.TextView |
|
lengthBuffer *cview.TextView |
|
slider *cview.Slider |
|
castInfoBuffer *cview.TextView |
|
|
|
editorCursor time.Duration |
|
editorCursorTime time.Time |
|
editorStatus string |
|
) |
|
|
|
func handleEditor() { |
|
go handleRead() |
|
go handleWrite() |
|
for c := range commandsIn { |
|
switch c.Type { |
|
case commandStatus: |
|
editorCursor = c.D |
|
editorCursorTime = time.Now() |
|
editorStatus = c.S |
|
} |
|
} |
|
} |
|
|
|
func connectToViewer(address string) error { |
|
var err error |
|
if strings.HasPrefix(address, "/") { |
|
commandConn, err = net.Dial("unix", address) |
|
} else { |
|
commandConn, err = net.Dial("tcp", address) |
|
} |
|
if err != nil { |
|
return err |
|
} |
|
|
|
go handleEditor() |
|
return nil |
|
} |
|
|
|
func updateStatus() { |
|
cursor := editorCursor |
|
if !editorPaused && editorStatus == "playing" { |
|
cursor += time.Since(editorCursorTime) |
|
} |
|
ms := ((cursor.Nanoseconds() / int64(time.Millisecond)) % 1000) / 100 |
|
statusBuffer.SetText(fmt.Sprintf("%d:%02d.%d", int(cursor.Minutes()), int(cursor.Seconds())%60, ms)) |
|
} |
|
|
|
func updateSlider() { |
|
lengthBuffer.SetText(fmt.Sprintf("%d:%02d", int(loadedCastLength.Minutes()), int(loadedCastLength.Seconds())%60)) |
|
slider.SetProgress(int(float64(editorCursor) / float64(loadedCastLength) * 100)) |
|
} |
|
|
|
func handleUpdateUI() { |
|
t := time.NewTicker(100 * time.Millisecond) |
|
i := 0 |
|
for range t.C { |
|
updateStatus() |
|
if i == 0 { |
|
updateSlider() |
|
} |
|
app.Draw() |
|
i++ |
|
if i > 9 { |
|
i = 0 |
|
} |
|
} |
|
} |
|
|
|
func runEditor(controlAddress string, force bool) { |
|
filePath := flag.Arg(0) |
|
if filePath == "" { |
|
log.Fatal("supply the path to an asciinema .cast file") |
|
} |
|
|
|
if !single { |
|
err := connectToViewer(controlAddress) |
|
if err != nil { |
|
if !single { |
|
log.Fatalf("failed to connect to viwer: %s", err) |
|
} |
|
err = nil |
|
} |
|
} |
|
|
|
app = cview.NewApplication() |
|
err := app.Init() |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
|
|
if single { |
|
app.QueueUpdate(func() { |
|
w, h := app.GetScreenSize() |
|
if (uint(w) < loadedCast.Header.Width || uint(h) < loadedCast.Header.Height) && !force { |
|
log.Fatalf("recording dimensions (%dx%d) are larger than the current terminal (%dx%d). add --force to force playback", loadedCast.Header.Width, loadedCast.Header.Height, w, h) |
|
} |
|
}) |
|
} |
|
|
|
app.EnableMouse(true) |
|
|
|
statusBuffer = cview.NewTextView() |
|
|
|
lengthBuffer = cview.NewTextView() |
|
lengthBuffer.SetTextAlign(cview.AlignRight) |
|
lengthBuffer.SetText("0:00") |
|
|
|
sliderChanged := func(value int) { |
|
c := time.Duration(float64(loadedCastLength) * (float64(value) / 100)) |
|
sendCommand(&command{commandPlay, c, ""}) |
|
} |
|
|
|
slider = cview.NewSlider() |
|
slider.SetChangedFunc(sliderChanged) |
|
|
|
castInfoBuffer = cview.NewTextView() |
|
castInfoBuffer.SetTextAlign(cview.AlignCenter) |
|
|
|
grid := cview.NewGrid() |
|
grid.SetRows(-1, 1, 3, -1) |
|
grid.AddItem(cview.NewTextView(), 0, 0, 1, 2, 0, 0, true) |
|
grid.AddItem(statusBuffer, 1, 0, 1, 1, 0, 0, false) |
|
grid.AddItem(lengthBuffer, 1, 1, 1, 1, 0, 0, false) |
|
grid.AddItem(slider, 2, 0, 1, 2, 0, 0, true) |
|
grid.AddItem(castInfoBuffer, 3, 0, 1, 2, 0, 0, true) |
|
grid.AddItem(cview.NewTextView(), 4, 0, 1, 2, 0, 0, true) |
|
|
|
app.SetRoot(grid, true) |
|
|
|
quit := func(ev *tcell.EventKey) *tcell.EventKey { |
|
app.Stop() |
|
return nil |
|
} |
|
|
|
doInterrupt := func(ev *tcell.EventKey) *tcell.EventKey { |
|
interruptPlayback() |
|
return nil |
|
} |
|
|
|
doPlay := func(ev *tcell.EventKey) *tcell.EventKey { |
|
editorCursorTime = time.Now() |
|
sendCommand(&command{commandPlay, 0, ""}) |
|
return nil |
|
} |
|
|
|
doPause := func(ev *tcell.EventKey) *tcell.EventKey { |
|
editorPaused = !editorPaused |
|
if editorPaused { |
|
sendCommand(&command{commandPause, 0, ""}) |
|
} else { |
|
editorCursorTime = time.Now() |
|
sendCommand(&command{commandResume, 0, ""}) |
|
} |
|
return nil |
|
} |
|
|
|
doFastForward := func(ev *tcell.EventKey) *tcell.EventKey { |
|
c := editorCursor + 5*time.Second |
|
editorCursor = c |
|
sendCommand(&command{commandPlay, c, ""}) |
|
return nil |
|
} |
|
|
|
doRewind := func(ev *tcell.EventKey) *tcell.EventKey { |
|
c := editorCursor - 5*time.Second |
|
if c < 0 { |
|
c = 0 |
|
} |
|
editorCursor = c |
|
sendCommand(&command{commandPlay, c, ""}) |
|
return nil |
|
} |
|
|
|
inputConfig := cbind.NewConfiguration() |
|
inputConfig.Set("Escape", quit) |
|
inputConfig.Set("Ctrl+c", quit) |
|
inputConfig.Set("Ctrl+d", doInterrupt) |
|
inputConfig.Set("Space", doPause) |
|
inputConfig.Set("Enter", doPlay) |
|
inputConfig.Set("Left", doRewind) |
|
inputConfig.Set("Right", doFastForward) |
|
|
|
app.SetInputCapture(inputConfig.Capture) |
|
|
|
go handleUpdateUI() |
|
|
|
err = loadCast(filePath) |
|
if err != nil { |
|
log.Fatalf("failed to load cast at %s: %s", filePath, err) |
|
} |
|
|
|
castInfoBuffer.SetText(path.Base(filePath)) |
|
|
|
castCommand <- &command{commandLoad, 0, filePath} |
|
castCommand <- &command{commandPlay, 0, ""} |
|
|
|
if err := app.Run(); err != nil { |
|
log.Fatal(err) |
|
} |
|
}
|
|
|