diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index c875707..a6c2cf3 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,6 +16,6 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
- version: v1.46.2
+ version: v1.51.2
args: -c .golang-ci.yml
diff --git a/.golang-ci.yml b/.golang-ci.yml
index 0668433..283529f 100644
--- a/.golang-ci.yml
+++ b/.golang-ci.yml
@@ -19,3 +19,13 @@ linters:
- predeclared
- nilnil
- varnamelen
+ - nosnakecase
+ - varcheck
+ - interfacer
+ - structcheck
+ - golint
+ - scopelint
+ - deadcode
+ - ifshort
+ - rowserrcheck
+ - wastedassign
diff --git a/.goreleaser.yml b/.goreleaser.yml
index ba58b09..2c8ba4e 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -17,12 +17,14 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}}
archives:
-- replacements:
- darwin: Darwin
- linux: Linux
- windows: Windows
- 386: i386
- amd64: x86_64
+ - id: replace
+ name_template: >-
+ {{- .ProjectName }}_
+ {{- title .Os }}_
+ {{- if eq .Arch "amd64" }}x86_64
+ {{- else if eq .Arch "386" }}i386
+ {{- else }}{{ .Arch }}{{ end }}
+ {{- if .Arm }}v{{ .Arm }}{{ end -}}
checksum:
name_template: 'checksums.txt'
snapshot:
diff --git a/README.md b/README.md
index 4459df2..91f831a 100644
--- a/README.md
+++ b/README.md
@@ -29,10 +29,10 @@ This fork allows to use viddy to show output data in different juju commands.
| key | |
|-----------|--------------------------------------------|
| SPACE | Toggle time machine mode |
-| s | Toggle suspend execution |
-| b | Toggle ring terminal bell |
-| d | Toggle diff |
-| t | Toggle header display |
+| s | Toggle suspend execution |
+| b | Toggle ring terminal bell |
+| d | Toggle diff |
+| t | Toggle header/title display |
| ? or h | Toggle help view |
| / | Search text |
| j | Pager: next line |
@@ -48,6 +48,28 @@ This fork allows to use viddy to show output data in different juju commands.
| Shift-O | (Time machine mode) Go to oldest position |
| Shift-N | (Time machine mode) Go to current position |
+## Configuration
+
+Install your config file on `$XDG_CONFIG_HOME/viddy.toml`
+On macOS, the path is `~/Library/Application\ Support/viddy.toml`.
+
+```toml
+[general]
+no_shell = false
+shell = "zsh"
+shell_options = ""
+
+[keymap]
+timemachine_go_to_past = "Down"
+timemachine_go_to_more_past = "Shift-Down"
+timemachine_go_to_future = "Up"
+timemachine_go_to_more_future = "Shift-Up"
+timemachine_go_to_now = "Ctrl-Shift-Up"
+timemachine_go_to_oldest = "Ctrl-Shift-Down"
+
+[color]
+background = "white" # Default value is inherit from terminal color.
+```
## What is "viddy" ?
diff --git a/config.go b/config.go
index 6677bb4..fb623b5 100644
--- a/config.go
+++ b/config.go
@@ -38,14 +38,16 @@ type runtimeConfig struct {
}
type general struct {
- shell string
- shellOptions string
- debug bool
- bell bool
- differences bool
- noTitle bool
- pty bool
- unfold bool
+ noShell bool
+ shell string
+ shellOptions string
+ debug bool
+ bell bool
+ differences bool
+ skipEmptyDiffs bool
+ noTitle bool
+ pty bool
+ unfold bool
}
type theme struct {
@@ -87,8 +89,10 @@ func NewConfig(v *viper.Viper, args []string) (*config, error) {
// general
flagSet.BoolP("bell", "b", false, "ring terminal bell changes between updates")
flagSet.BoolP("differences", "d", false, "highlight changes between updates")
+ flagSet.BoolP("skip-empty-diffs", "s", false, "skip snapshots with no changes (+0 -0) in history")
flagSet.BoolP("no-title", "t", false, "turn off header")
flagSet.Bool("debug", false, "")
+ flagSet.Bool("no-shell", false, "do not use a shell even if --shell is set")
flagSet.String("shell", "", "shell (default \"sh\")")
flagSet.String("shell-options", "", "additional shell options")
flagSet.Bool("unfold", false, "unfold")
@@ -130,6 +134,10 @@ func NewConfig(v *viper.Viper, args []string) (*config, error) {
return nil, err
}
+ if err := v.BindPFlag("general.no_shell", flagSet.Lookup("no-shell")); err != nil {
+ return nil, err
+ }
+
if err := v.BindPFlag("general.shell", flagSet.Lookup("shell")); err != nil {
return nil, err
}
@@ -148,6 +156,10 @@ func NewConfig(v *viper.Viper, args []string) (*config, error) {
return nil, err
}
+ if err := v.BindPFlag("general.skip_empty_diffs", flagSet.Lookup("skip-empty-diffs")); err != nil {
+ return nil, err
+ }
+
if err := v.BindPFlag("general.no_title", flagSet.Lookup("no-title")); err != nil {
return nil, err
}
@@ -161,10 +173,12 @@ func NewConfig(v *viper.Viper, args []string) (*config, error) {
}
conf.general.debug = v.GetBool("general.debug")
+ conf.general.noShell = v.GetBool("general.no_shell")
conf.general.shell = v.GetString("general.shell")
conf.general.shellOptions = v.GetString("general.shell_options")
conf.general.bell, _ = flagSet.GetBool("bell")
conf.general.differences, _ = flagSet.GetBool("differences")
+ conf.general.skipEmptyDiffs, _ = flagSet.GetBool("skip-empty-diffs")
conf.general.noTitle, _ = flagSet.GetBool("no-title")
conf.general.unfold = v.GetBool("general.unfold")
conf.general.pty = v.GetBool("general.pty")
@@ -295,7 +309,7 @@ type parseKeyStrokeError struct {
}
func (e parseKeyStrokeError) Error() string {
- return fmt.Sprintf("connot parse key: %q", e.key)
+ return fmt.Sprintf("cannot parse key: %q", e.key)
}
// ParseKeyStroke parse string describing key.
diff --git a/generator.go b/generator.go
index 5c35040..99cbcfb 100644
--- a/generator.go
+++ b/generator.go
@@ -4,8 +4,10 @@ import "time"
type newSnapFunc func(int64, *Snapshot, chan<- struct{}) *Snapshot
-func ClockSnapshot(begin int64, newSnap newSnapFunc, interval time.Duration) <-chan *Snapshot {
+func ClockSnapshot(begin int64, newSnap newSnapFunc, interval time.Duration) (<-chan *Snapshot, chan<- bool) {
c := make(chan *Snapshot)
+ isSuspended := false
+ isSuspendedQueue := make(chan bool)
go func() {
var s *Snapshot
@@ -13,6 +15,15 @@ func ClockSnapshot(begin int64, newSnap newSnapFunc, interval time.Duration) <-c
t := time.Tick(interval)
for now := range t {
+ select {
+ case isSuspended = <-isSuspendedQueue:
+ default:
+ }
+
+ if isSuspended {
+ continue
+ }
+
finish := make(chan struct{})
id := (now.UnixNano() - begin) / int64(time.Millisecond)
s = newSnap(id, s, finish)
@@ -20,11 +31,13 @@ func ClockSnapshot(begin int64, newSnap newSnapFunc, interval time.Duration) <-c
}
}()
- return c
+ return c, isSuspendedQueue
}
-func PreciseSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Snapshot {
+func PreciseSnapshot(newSnap newSnapFunc, interval time.Duration) (<-chan *Snapshot, chan<- bool) {
c := make(chan *Snapshot)
+ isSuspended := false
+ isSuspendedQueue := make(chan bool)
go func() {
var s *Snapshot
@@ -32,6 +45,17 @@ func PreciseSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Snapsh
begin := time.Now().UnixNano()
for {
+ select {
+ case isSuspended = <-isSuspendedQueue:
+ default:
+ }
+
+ if isSuspended {
+ time.Sleep(interval)
+
+ continue
+ }
+
finish := make(chan struct{})
start := time.Now()
id := (start.UnixNano() - begin) / int64(time.Millisecond)
@@ -52,11 +76,13 @@ func PreciseSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Snapsh
}
}()
- return c
+ return c, isSuspendedQueue
}
-func SequentialSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Snapshot {
+func SequentialSnapshot(newSnap newSnapFunc, interval time.Duration) (<-chan *Snapshot, chan<- bool) {
c := make(chan *Snapshot)
+ isSuspended := false
+ isSuspendedQueue := make(chan bool)
go func() {
var s *Snapshot
@@ -64,6 +90,17 @@ func SequentialSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Sna
begin := time.Now().UnixNano()
for {
+ select {
+ case isSuspended = <-isSuspendedQueue:
+ default:
+ }
+
+ if isSuspended {
+ time.Sleep(interval)
+
+ continue
+ }
+
finish := make(chan struct{})
id := (time.Now().UnixNano() - begin) / int64(time.Millisecond)
s = newSnap(id, s, finish)
@@ -75,5 +112,5 @@ func SequentialSnapshot(newSnap newSnapFunc, interval time.Duration) <-chan *Sna
}
}()
- return c
+ return c, isSuspendedQueue
}
diff --git a/go.mod b/go.mod
index 006c25b..74f5fbc 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
github.com/creack/pty v1.1.15
github.com/fatih/color v1.12.0
github.com/gdamore/tcell/v2 v2.5.1
+ github.com/mattn/go-runewidth v0.0.13
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
github.com/rivo/tview v0.0.0-20220610163003-691f46d6f500
github.com/sergi/go-diff v1.2.0
@@ -30,7 +31,6 @@ require (
github.com/magiconair/properties v1.8.5 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
- github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -38,11 +38,11 @@ require (
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
- golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
- golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
- golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
- golang.org/x/text v0.3.7 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/term v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ gopkg.in/yaml.v3 v3.0.0 // indirect
)
diff --git a/go.sum b/go.sum
index 3b66119..7b019c9 100644
--- a/go.sum
+++ b/go.sum
@@ -80,9 +80,6 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
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.4.1-0.20210904044819-ae5116d72813 h1:uqlt4EHPdtAAXKBq6OKc0auHsQP5zfhdHo/BYe6hz2Q=
-github.com/gdamore/tcell/v2 v2.4.1-0.20210904044819-ae5116d72813/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
-github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
github.com/gdamore/tcell/v2 v2.5.1 h1:zc3LPdpK184lBW7syF2a5C6MV827KmErk9jGVnmsl/I=
github.com/gdamore/tcell/v2 v2.5.1/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
@@ -237,8 +234,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b h1:EMgbQ+bOHWkl0Ptano8M0yrzVZkxans+Vfv7ox/EtO8=
-github.com/rivo/tview v0.0.0-20211202162923-2a6de950f73b/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/tview v0.0.0-20220610163003-691f46d6f500 h1:KvoRB2TMfMqK2NF2mIvZprDT/Ofvsa4RphWLoCmUDag=
github.com/rivo/tview v0.0.0-20220610163003-691f46d6f500/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@@ -246,8 +241,6 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sachaos/tview v0.0.0-20210909084047-7f6f0b84f61c h1:OYO8SfrdARTchNFYwtSpQf1pY7FeZRY85PuOh0akRmQ=
-github.com/sachaos/tview v0.0.0-20210909084047-7f6f0b84f61c/go.mod h1:NpihEwB0BMEMry10psTJveTy9b5l3GMVwBuVpKR3Jo0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@@ -374,8 +367,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -444,17 +438,14 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210903071746-97244b99971b h1:3Dq0eVHn0uaQJmPO+/aYPI/fRMqdrVDbu7MQcku54gg=
-golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
-golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
-golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -463,8 +454,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -640,8 +632,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..e69de29
diff --git a/snapshot.go b/snapshot.go
index d21993e..9683c70 100644
--- a/snapshot.go
+++ b/snapshot.go
@@ -24,6 +24,7 @@ type Snapshot struct {
command string
args []string
+ noShell bool
shell string
shellOpts string
@@ -50,12 +51,13 @@ type Snapshot struct {
// NewSnapshot returns Snapshot object.
//
//nolint:lll
-func NewSnapshot(id int64, command string, args []string, shell string, shellOpts string, before *Snapshot, finish chan<- struct{}) *Snapshot {
+func NewSnapshot(id int64, command string, args []string, noShell bool, shell string, shellOpts string, before *Snapshot, finish chan<- struct{}) *Snapshot {
return &Snapshot{
id: id,
command: command,
args: args,
+ noShell: noShell,
shell: shell,
shellOpts: shellOpts,
@@ -97,22 +99,25 @@ func (s *Snapshot) compareFromBefore() error {
return nil
}
+//nolint:gosec
func (s *Snapshot) prepareCommand(commands []string) *exec.Cmd {
- var command *exec.Cmd
-
if runtime.GOOS == "windows" {
cmdStr := strings.Join(commands, " ")
compSec := os.Getenv("COMSPEC")
- command = exec.Command(compSec, "/c", cmdStr)
- } else {
- var args []string
- args = append(args, strings.Fields(s.shellOpts)...)
- args = append(args, "-c")
- args = append(args, strings.Join(commands, " "))
- command = exec.Command(s.shell, args...) //nolint:gosec
+
+ return exec.Command(compSec, "/c", cmdStr)
}
- return command
+ if s.noShell {
+ return exec.Command(commands[0], commands[1:]...)
+ }
+
+ var args []string
+ args = append(args, strings.Fields(s.shellOpts)...)
+ args = append(args, "-c")
+ args = append(args, strings.Join(commands, " "))
+
+ return exec.Command(s.shell, args...)
}
func isWhiteString(str string) bool {
@@ -169,7 +174,7 @@ func DiffPrettyText(diffs []diffmatchpatch.Diff) string {
if unicode.IsSpace(c) {
_, _ = buff.WriteRune(c)
} else {
- _, _ = buff.WriteString(color.New(color.BgGreen).Sprintf(string(c)))
+ _, _ = buff.WriteString(color.New(color.FgBlack).Sprintf(color.New(color.BgGreen).Sprintf(string(c))))
}
}
case diffmatchpatch.DiffEqual:
diff --git a/viddy.go b/viddy.go
index 0d67806..89d244e 100644
--- a/viddy.go
+++ b/viddy.go
@@ -9,7 +9,6 @@ import (
"html/template"
"io"
"os"
- "sort"
"strconv"
"strings"
"sync"
@@ -45,12 +44,12 @@ type Viddy struct {
timeView *tview.TextView
historyView *tview.Table
historyRows map[int64]*HistoryRow
- sync.RWMutex
// bWidth store current pty width.
bWidth atomic.Value
- idList []int64
+ // id -> row count (as of just after the snapshot was added).
+ historyRowCount map[int64]int
bodyView *tview.TextView
app *tview.Application
@@ -59,10 +58,11 @@ type Viddy struct {
statusView *tview.TextView
queryEditor *tview.InputField
- snapshotQueue <-chan *Snapshot
- queue chan int64
- finishedQueue chan int64
- diffQueue chan int64
+ snapshotQueue <-chan *Snapshot
+ isSuspendedQueue chan<- bool
+ queue chan int64
+ finishedQueue chan int64
+ diffQueue chan int64
currentID int64
latestFinishedID int64
@@ -71,6 +71,7 @@ type Viddy struct {
isNoTitle bool
isRingBell bool
isShowDiff bool
+ skipEmptyDiffs bool
isEditQuery bool
unfold bool
pty bool
@@ -102,18 +103,30 @@ func NewViddy(conf *config) *Viddy {
begin := time.Now().UnixNano()
newSnap := func(id int64, before *Snapshot, finish chan<- struct{}) *Snapshot {
- return NewSnapshot(id, conf.runtime.cmd, conf.runtime.args, conf.general.shell, conf.general.shellOptions, before, finish)
- }
-
- var snapshotQueue <-chan *Snapshot
+ return NewSnapshot(
+ id,
+ conf.runtime.cmd,
+ conf.runtime.args,
+ conf.general.noShell,
+ conf.general.shell,
+ conf.general.shellOptions,
+ before,
+ finish,
+ )
+ }
+
+ var (
+ snapshotQueue <-chan *Snapshot
+ isSuspendedQueue chan<- bool
+ )
switch conf.runtime.mode {
case ViddyIntervalModeClockwork:
- snapshotQueue = ClockSnapshot(begin, newSnap, conf.runtime.interval)
+ snapshotQueue, isSuspendedQueue = ClockSnapshot(begin, newSnap, conf.runtime.interval)
case ViddyIntervalModeSequential:
- snapshotQueue = SequentialSnapshot(newSnap, conf.runtime.interval)
+ snapshotQueue, isSuspendedQueue = SequentialSnapshot(newSnap, conf.runtime.interval)
case ViddyIntervalModePrecise:
- snapshotQueue = PreciseSnapshot(newSnap, conf.runtime.interval)
+ snapshotQueue, isSuspendedQueue = PreciseSnapshot(newSnap, conf.runtime.interval)
}
return &Viddy{
@@ -126,17 +139,21 @@ func NewViddy(conf *config) *Viddy {
snapshots: sync.Map{},
historyRows: map[int64]*HistoryRow{},
- snapshotQueue: snapshotQueue,
- queue: make(chan int64),
- finishedQueue: make(chan int64),
- diffQueue: make(chan int64, 100),
+ historyRowCount: map[int64]int{},
- isRingBell: conf.general.bell,
- isShowDiff: conf.general.differences,
- isNoTitle: conf.general.noTitle,
- isDebug: conf.general.debug,
- unfold: conf.general.unfold,
- pty: conf.general.pty,
+ snapshotQueue: snapshotQueue,
+ isSuspendedQueue: isSuspendedQueue,
+ queue: make(chan int64),
+ finishedQueue: make(chan int64),
+ diffQueue: make(chan int64, 100),
+
+ isRingBell: conf.general.bell,
+ isShowDiff: conf.general.differences,
+ skipEmptyDiffs: conf.general.skipEmptyDiffs,
+ isNoTitle: conf.general.noTitle,
+ isDebug: conf.general.debug,
+ unfold: conf.general.unfold,
+ pty: conf.general.pty,
currentID: -1,
latestFinishedID: -1,
@@ -175,14 +192,16 @@ func (v *Viddy) SetIsNoTitle(b bool) {
func (v *Viddy) SetIsShowDiff(b bool) {
v.isShowDiff = b
- v.setSelection(v.currentID)
+ v.setSelection(v.currentID, -1)
v.arrange()
}
func (v *Viddy) SetIsTimeMachine(b bool) {
v.isTimeMachine = b
if !v.isTimeMachine {
- v.setSelection(v.latestFinishedID)
+ v.setSelection(v.latestFinishedID, -1)
+ } else {
+ v.goToNowOnTimeMachine()
}
v.arrange()
@@ -205,6 +224,26 @@ func (v *Viddy) startRunner() {
}
}
+func (v *Viddy) updateSelection() {
+ if !v.isTimeMachine {
+ v.setSelection(v.latestFinishedID, -1)
+ } else {
+ v.setSelection(v.currentID, -1)
+ }
+}
+
+func (v *Viddy) addSnapshotToView(id int64, r *HistoryRow) {
+ v.historyView.InsertRow(0)
+ v.historyView.SetCell(0, 0, r.id)
+ v.historyView.SetCell(0, 1, r.addition)
+ v.historyView.SetCell(0, 2, r.deletion)
+ v.historyView.SetCell(0, 3, r.exitCode)
+
+ v.historyRowCount[id] = v.historyView.GetRowCount()
+
+ v.updateSelection()
+}
+
func (v *Viddy) diffQueueHandler() {
for {
func() {
@@ -225,10 +264,12 @@ func (v *Viddy) diffQueueHandler() {
return
}
- if v.isRingBell {
- if s.diffAdditionCount > 0 || s.diffDeletionCount > 0 {
+ if s.diffAdditionCount > 0 || s.diffDeletionCount > 0 {
+ if v.isRingBell {
fmt.Print(string(byte(7)))
}
+ } else if v.skipEmptyDiffs {
+ return
}
r, ok := v.historyRows[id]
@@ -236,13 +277,19 @@ func (v *Viddy) diffQueueHandler() {
return
}
+ // if skipEmptyDiffs is true, queueHandler wouldn't have added the
+ // snapshot to view, so we need to add it here.
+ if v.skipEmptyDiffs {
+ v.addSnapshotToView(id, r)
+ }
+
r.addition.SetText("+" + strconv.Itoa(s.diffAdditionCount))
r.deletion.SetText("-" + strconv.Itoa(s.diffDeletionCount))
}()
}
}
-//nolint:funlen,gocognit,cyclop
+//nolint:funlen
func (v *Viddy) queueHandler() {
for {
func() {
@@ -271,51 +318,50 @@ func (v *Viddy) queueHandler() {
ls := v.getSnapShot(v.latestFinishedID)
if ls == nil || s.start.After(ls.start) {
v.latestFinishedID = id
- if !v.isTimeMachine {
- v.setSelection(id)
- } else {
- v.setSelection(v.currentID)
- }
+ v.updateSelection()
}
case id := <-v.queue:
- if v.isSuspend {
- return
- }
-
s := v.getSnapShot(id)
idCell := tview.NewTableCell(strconv.FormatInt(s.id, 10)).SetTextColor(tview.Styles.SecondaryTextColor)
additionCell := tview.NewTableCell("").SetTextColor(tcell.ColorGreen)
deletionCell := tview.NewTableCell("").SetTextColor(tcell.ColorRed)
exitCodeCell := tview.NewTableCell("").SetTextColor(tcell.ColorYellow)
- v.historyRows[s.id] = &HistoryRow{
+ r := &HistoryRow{
id: idCell,
addition: additionCell,
deletion: deletionCell,
exitCode: exitCodeCell,
}
-
- v.historyView.InsertRow(0)
- v.historyView.SetCell(0, 0, idCell)
- v.historyView.SetCell(0, 1, additionCell)
- v.historyView.SetCell(0, 2, deletionCell)
- v.historyView.SetCell(0, 3, exitCodeCell)
-
- v.Lock()
- v.idList = append(v.idList, id)
- v.Unlock()
-
- if !v.isTimeMachine {
- v.setSelection(v.latestFinishedID)
- } else {
- v.setSelection(v.currentID)
+ v.historyRows[s.id] = r
+
+ // if skipEmptyDiffs is true, we need to check if the snapshot
+ // is empty before adding it to the view (in diffQueueHandler).
+ //
+ // This means we're trading off two things:
+ //
+ // 1. We're not showing the snapshot in history view until the
+ // command finishes running, which means it's not possible
+ // to see partial output.
+ // 2. Order of the snapshots in history view is lost
+ // (in non-sequential modes), as some commands could finish
+ // running quicker than others for whatever reason.
+ //
+ // It of course is possible to address these issues by adding
+ // all snapshots to the history view and then removing the empty
+ // ones but it unnecessarily complicates the implementation.
+ if !v.skipEmptyDiffs {
+ v.addSnapshotToView(id, r)
}
}
}()
}
}
-func (v *Viddy) setSelection(id int64) {
+// setSelection selects the given row in the history view. If row is -1, it will
+// attempt to select the row corresponding to the given id (or default to the
+// latest row if id doesn't exist).
+func (v *Viddy) setSelection(id int64, row int) {
if id == -1 {
return
}
@@ -327,14 +373,11 @@ func (v *Viddy) setSelection(id int64) {
v.historyView.SetSelectable(true, false)
}
- v.RLock()
- index := sort.Search(len(v.idList), func(i int) bool {
- return v.idList[i] >= id
- })
- i := len(v.idList) - index - 1
- v.RUnlock()
+ if row == -1 {
+ row = v.historyView.GetRowCount() - v.historyRowCount[id]
+ }
- v.historyView.Select(i, 0)
+ v.historyView.Select(row, 0)
v.currentID = id
unix := v.begin + id*int64(time.Millisecond)
v.timeView.SetText(time.Unix(unix/int64(time.Second), unix%int64(time.Second)).String())
@@ -433,6 +476,7 @@ func (v *Viddy) arrange() {
}
// Run is entry point to run viddy.
+//
// nolint: funlen,gocognit,cyclop
func (v *Viddy) Run() error {
b := tview.NewTextView()
@@ -574,20 +618,40 @@ func (v *Viddy) Run() error {
any = true
}
- if event.Key() == tcell.KeyEsc || event.Rune() == 'q' {
+ if event.Key() == tcell.KeyEsc {
v.showHelpView = false
v.arrange()
}
+ if event.Rune() == 'q' {
+ if v.showHelpView { // if it's help mode, just go back
+ v.showHelpView = false
+ v.arrange()
+ } else { // it's not help view, so just quit
+ v.app.Stop()
+ os.Exit(0)
+ }
+ }
+
+ // quit viddy from any view
+ if event.Rune() == 'Q' {
+ v.app.Stop()
+ os.Exit(0)
+ }
+
switch event.Rune() {
case 's':
v.isSuspend = !v.isSuspend
+ v.isSuspendedQueue <- v.isSuspend
case 'b':
v.isRingBell = !v.isRingBell
//case 'd':
// v.SetIsShowDiff(!v.isShowDiff)
case 't':
v.SetIsNoTitle(!v.isNoTitle)
+ case 'u':
+ b.SetWrap(v.unfold)
+ v.unfold = !v.unfold
case 'x':
if v.isDebug {
v.ShowLogView(!v.showLogView)
@@ -618,8 +682,6 @@ func (v *Viddy) Run() error {
v.UpdateStatusView()
- app.EnableMouse(true)
-
v.app = app
v.arrange()
@@ -632,87 +694,64 @@ func (v *Viddy) Run() error {
return app.Run()
}
-func (v *Viddy) goToPastOnTimeMachine() {
- count := v.historyView.GetRowCount()
- selection, _ := v.historyView.GetSelection()
+func (v *Viddy) goToRow(row int) {
+ if row < 0 {
+ row = 0
+ } else if count := v.historyView.GetRowCount(); row >= count {
+ row = count - 1
+ }
- if selection+1 < count {
- cell := v.historyView.GetCell(selection+1, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
+ var (
+ cell = v.historyView.GetCell(row, 0)
+ id, err = strconv.ParseInt(cell.Text, 10, 64)
+ )
+
+ if err == nil { // if _no_ error
+ v.setSelection(id, row)
}
}
+func (v *Viddy) goToPastOnTimeMachine() {
+ selection, _ := v.historyView.GetSelection()
+ v.goToRow(selection + 1)
+}
+
func (v *Viddy) goToFutureOnTimeMachine() {
selection, _ := v.historyView.GetSelection()
- if 0 <= selection-1 {
- cell := v.historyView.GetCell(selection-1, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
- }
+ v.goToRow(selection - 1)
}
func (v *Viddy) goToMorePastOnTimeMachine() {
- count := v.historyView.GetRowCount()
selection, _ := v.historyView.GetSelection()
-
- if selection+10 < count {
- cell := v.historyView.GetCell(selection+10, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
- } else {
- cell := v.historyView.GetCell(count-1, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
- }
+ v.goToRow(selection + 10)
}
func (v *Viddy) goToMoreFutureOnTimeMachine() {
selection, _ := v.historyView.GetSelection()
- if 0 <= selection-10 {
- cell := v.historyView.GetCell(selection-10, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
- } else {
- cell := v.historyView.GetCell(0, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
- }
+ v.goToRow(selection - 10)
}
func (v *Viddy) goToNowOnTimeMachine() {
- cell := v.historyView.GetCell(0, 0)
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
+ v.goToRow(0)
}
func (v *Viddy) goToOldestOnTimeMachine() {
- count := v.historyView.GetRowCount()
- cell := v.historyView.GetCell(count-1, 0)
-
- if id, err := strconv.ParseInt(cell.Text, 10, 64); err == nil {
- v.setSelection(id)
- }
+ v.goToRow(v.historyView.GetRowCount() - 1)
}
-var helpTemplate = `Press ESC or Q to go back
+var helpTemplate = `Press ESC or q to go back
[::b]Key Bindings[-:-:-]
- [::u]General[-:-:-]
+ [::u]General[-:-:-]
Toggle time machine mode : [yellow]SPACE[-:-:-]
Toggle suspend execution : [yellow]s[-:-:-]
Toggle ring terminal bell : [yellow]b[-:-:-]
Toggle header display : [yellow]t[-:-:-]
Toggle help view : [yellow]h[-:-:-], [yellow]?[-:-:-]
+ Toggle unfold : [yellow]u[-:-:-]
+ Quit Viddy : [yellow]Q[-:-:-]
[::u]Pager[-:-:-]