Initial commit
This commit is contained in:
commit
e08e836dd7
|
@ -0,0 +1,4 @@
|
|||
.idea/
|
||||
dist/
|
||||
*.sh
|
||||
asciinema-editor
|
|
@ -0,0 +1,26 @@
|
|||
image: golang:latest
|
||||
|
||||
stages:
|
||||
- validate
|
||||
- build
|
||||
|
||||
fmt:
|
||||
stage: validate
|
||||
script:
|
||||
- gofmt -l -s -e .
|
||||
- exit $(gofmt -l -s -e . | wc -l)
|
||||
|
||||
vet:
|
||||
stage: validate
|
||||
script:
|
||||
- go vet -composites=false ./...
|
||||
|
||||
test:
|
||||
stage: validate
|
||||
script:
|
||||
- go test -race -v ./...
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- go build
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Trevor Slocum <trevor@rocketnine.space>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,40 @@
|
|||
# asciinema-editor
|
||||
[![CI status](https://gitlab.com/tslocum/asciinema-editor/badges/master/pipeline.svg)](https://gitlab.com/tslocum/asciinema-editor/commits/master)
|
||||
[![Donate](https://img.shields.io/liberapay/receives/rocketnine.space.svg?logo=liberapay)](https://liberapay.com/rocketnine.space)
|
||||
|
||||
Interactive [asciicast](https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md) editor
|
||||
|
||||
## Features
|
||||
|
||||
- WIP
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get gitlab.com/tslocum/asciinema-editor
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Execute `asciinema-editor` twice to edit a screencast.
|
||||
|
||||
```bash
|
||||
asciinema-editor --viewer # Start viewer
|
||||
asciinema-editor /tmp/path/to/screen.cast # Start editor
|
||||
```
|
||||
|
||||
**Tip:** A terminal multiplexer such as [tmux](https://github.com/tmux/tmux) or [screen](https://www.gnu.org/software/screen/)
|
||||
makes switching between instances easier.
|
||||
|
||||
**Note:** It is possible (but not recommended) to edit a cast using a single
|
||||
editor instance by providing the argument `--single`. It is not possible to
|
||||
pause playback when using a single instance.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* [cirocosta/asciinema-edit](https://github.com/cirocosta/asciinema-edit)
|
||||
* [tslocum/cview](https://gitlab.com/tslocum/cview)
|
||||
|
||||
## Support
|
||||
|
||||
Please share issues and suggestions [here](https://gitlab.com/tslocum/asciinema-editor/issues).
|
|
@ -0,0 +1,35 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
func handleWrite() {
|
||||
var b []byte
|
||||
var err error
|
||||
for c := range commandsOut {
|
||||
b, err = json.Marshal(c)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to marshal %+v: %s", c, err)
|
||||
}
|
||||
commandConn.Write(append(b, byte('\n')))
|
||||
}
|
||||
}
|
||||
|
||||
func handleRead() {
|
||||
var c *Command
|
||||
scanner := bufio.NewScanner(commandConn)
|
||||
for scanner.Scan() {
|
||||
c = &Command{}
|
||||
err := json.Unmarshal(scanner.Bytes(), c)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to unmarshal %s: %s", string(scanner.Bytes()), err)
|
||||
}
|
||||
commandsIn <- c
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
var editorPaused bool
|
||||
|
||||
func handleEditor() {
|
||||
go handleRead()
|
||||
go handleWrite()
|
||||
for c := range commandsIn {
|
||||
log.Fatal(c)
|
||||
}
|
||||
}
|
||||
|
||||
func Connect(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 runEditor(viewer, single, force bool, controlAddress string) {
|
||||
filePath := flag.Arg(0)
|
||||
if filePath == "" {
|
||||
log.Fatal("supply the path to an asciinema .cast file")
|
||||
}
|
||||
|
||||
c, err := loadCast(filePath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load asciinema screencast: %s", err)
|
||||
}
|
||||
|
||||
err = Connect(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)
|
||||
}
|
||||
|
||||
app.QueueUpdate(func() {
|
||||
w, h := app.GetScreenSize()
|
||||
if (uint(w) < c.Header.Width || uint(h) < c.Header.Height) && !force {
|
||||
log.Fatalf("recording dimensions (%dx%d) are larger than the current terminal (%dx%d). add --force to force playback", c.Header.Width, c.Header.Height, w, h)
|
||||
}
|
||||
})
|
||||
|
||||
app.EnableMouse(true)
|
||||
|
||||
tv := cview.NewTextView()
|
||||
tv.SetText("WIP")
|
||||
|
||||
app.SetRoot(tv, true)
|
||||
|
||||
var cursor time.Duration
|
||||
|
||||
go func() {
|
||||
if single {
|
||||
app.Suspend(playFunc(c, cursor))
|
||||
} else if !viewer {
|
||||
sendCommand(&Command{CommandLoad, 0, filePath})
|
||||
sendCommand(&Command{CommandPlay, 30 * time.Second, ""})
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO loop, poll for current timestamp in commandsIn
|
||||
|
||||
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 {
|
||||
sendCommand(&Command{CommandPlay, 0, ""})
|
||||
return nil
|
||||
}
|
||||
|
||||
doPause := func(ev *tcell.EventKey) *tcell.EventKey {
|
||||
editorPaused = !editorPaused
|
||||
if editorPaused {
|
||||
sendCommand(&Command{CommandPause, 0, ""})
|
||||
} else {
|
||||
sendCommand(&Command{CommandResume, 0, ""})
|
||||
}
|
||||
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)
|
||||
|
||||
app.SetInputCapture(inputConfig.Capture)
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
module gitlab.com/tslocum/asciinema-editor
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/cirocosta/asciinema-edit v0.0.0-20190130154215-1c0971ae232a
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1
|
||||
github.com/onsi/ginkgo v1.14.2 // indirect
|
||||
github.com/onsi/gomega v1.10.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
gitlab.com/tslocum/cbind v0.1.3
|
||||
gitlab.com/tslocum/cview v1.5.1-0.20201023011306-a55c8124901e
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd
|
||||
)
|
|
@ -0,0 +1,90 @@
|
|||
github.com/cirocosta/asciinema-edit v0.0.0-20190130154215-1c0971ae232a h1:Bc9sl3YDXJNoo8IM9YFavXKXyrrLm2lbOTWRSQ7fUoo=
|
||||
github.com/cirocosta/asciinema-edit v0.0.0-20190130154215-1c0971ae232a/go.mod h1:GSeuTiGFjsG9dKuyYtHx9NE+Q9slTk/pkIUk5AJsXUs=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 h1:gp9ujdOQmQf1gMvqOYYgxdMS5tRpRGE3HAgRH4Hgzd4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
gitlab.com/tslocum/cbind v0.1.3 h1:FT/fTQ4Yj3eo5021lB3IbkIt8eVtYGhrw/xur+cjvUU=
|
||||
gitlab.com/tslocum/cbind v0.1.3/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI=
|
||||
gitlab.com/tslocum/cview v1.5.1-0.20201023011306-a55c8124901e h1:v+oeAUTHF3LuT4bjM3ZQVx5tpRPTVmowebqWAnqrkWo=
|
||||
gitlab.com/tslocum/cview v1.5.1-0.20201023011306-a55c8124901e/go.mod h1:IEfcfqcDG4Tm28XM+H8TEtbrqjpieAewfcJvQ9SgcRM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=
|
||||
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
var app *cview.Application
|
||||
|
||||
func main() {
|
||||
var (
|
||||
force bool
|
||||
viewer bool
|
||||
single bool
|
||||
controlAddress string
|
||||
)
|
||||
flag.BoolVar(&force, "force", false, "force playback with insufficiently sized terminal")
|
||||
flag.BoolVar(&viewer, "viewer", false, "run as viewer")
|
||||
flag.BoolVar(&single, "single", false, "run as editor without connecting to a remote viewer")
|
||||
flag.StringVar(&controlAddress, "control", "", "socket path or server address used to control viewer from editor")
|
||||
flag.Parse()
|
||||
|
||||
if controlAddress == "" {
|
||||
controlAddress = path.Join(os.TempDir(), "asciinema-editor.sock")
|
||||
}
|
||||
|
||||
if viewer {
|
||||
runViewer(controlAddress)
|
||||
return
|
||||
}
|
||||
|
||||
runEditor(viewer, single, force, controlAddress)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cirocosta/asciinema-edit/cast"
|
||||
)
|
||||
|
||||
var (
|
||||
playerCursor time.Duration
|
||||
interrupt = make(chan struct{})
|
||||
)
|
||||
|
||||
func loadCast(filePath string) (*cast.Cast, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := cast.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func playCast(c *cast.Cast, at time.Duration) {
|
||||
resetScreen()
|
||||
disableEcho()
|
||||
|
||||
start := time.Now().Add(at * -1)
|
||||
for _, ev := range c.EventStream {
|
||||
if ev.Type == "i" {
|
||||
continue // TODO
|
||||
}
|
||||
|
||||
select {
|
||||
case <-interrupt:
|
||||
playerCursor = time.Since(start)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
t := time.Duration(ev.Time * float64(time.Second))
|
||||
if time.Since(start) < t {
|
||||
t := time.NewTimer(t - time.Since(start))
|
||||
select {
|
||||
case <-interrupt:
|
||||
playerCursor = time.Since(start)
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
playerCursor = time.Since(start)
|
||||
}
|
||||
fmt.Print(ev.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func playFunc(c *cast.Cast, at time.Duration) func() {
|
||||
return func() {
|
||||
playCast(c, at)
|
||||
}
|
||||
}
|
||||
|
||||
func interruptPlayback() {
|
||||
select {
|
||||
case interrupt <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/cirocosta/asciinema-edit/cast"
|
||||
)
|
||||
|
||||
var (
|
||||
commandsIn = make(chan *Command)
|
||||
commandsOut = make(chan *Command)
|
||||
|
||||
commandListener net.Listener
|
||||
commandConn net.Conn
|
||||
|
||||
loadedCast *cast.Cast
|
||||
)
|
||||
|
||||
type CommandType int
|
||||
|
||||
const (
|
||||
CommandStatus = 1
|
||||
CommandLoad = 2
|
||||
CommandStop = 3
|
||||
CommandPlay = 4
|
||||
CommandPause = 5
|
||||
CommandResume = 6
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Type CommandType
|
||||
D time.Duration
|
||||
S string
|
||||
}
|
||||
|
||||
func sendCommand(c *Command) {
|
||||
commandsOut <- c
|
||||
}
|
||||
|
||||
func resetScreen() {
|
||||
cmd := exec.Command("tput", "reset")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Run()
|
||||
}
|
||||
|
||||
func disableEcho() {
|
||||
fd := int(os.Stdin.Fd())
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
termios.Lflag &^= unix.ECHO
|
||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Host(address string) error {
|
||||
var initialized bool
|
||||
var err error
|
||||
if strings.HasPrefix(address, "/") {
|
||||
commandListener, err = net.Listen("unix", address)
|
||||
} else {
|
||||
commandListener, err = net.Listen("tcp", address)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
commandConn, err = commandListener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !initialized {
|
||||
go handleViewer()
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleViewer() {
|
||||
go handleRead()
|
||||
go handleWrite()
|
||||
var err error
|
||||
for c := range commandsIn {
|
||||
switch c.Type {
|
||||
case CommandLoad:
|
||||
loadedCast, err = loadCast(c.S)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load cast at %s: %s", c.S, err)
|
||||
}
|
||||
case CommandPlay:
|
||||
interruptPlayback()
|
||||
go playCast(loadedCast, c.D)
|
||||
case CommandPause:
|
||||
interruptPlayback()
|
||||
case CommandResume:
|
||||
go playCast(loadedCast, playerCursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runViewer(controlAddress string) {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT)
|
||||
go func() {
|
||||
<-sigc
|
||||
interruptPlayback()
|
||||
resetScreen()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
err := Host(controlAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to host viwer: %s", err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue