Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

history: skip snapshots with no changes (+0 -0) #99

Merged
merged 4 commits into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ type runtimeConfig struct {
}

type general struct {
shell string
shellOptions string
debug bool
bell bool
differences bool
noTitle bool
pty bool
unfold bool
shell string
shellOptions string
debug bool
bell bool
differences bool
skipEmptyDiffs bool
noTitle bool
pty bool
unfold bool
}

type theme struct {
Expand Down Expand Up @@ -87,6 +88,7 @@ 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.String("shell", "", "shell (default \"sh\")")
Expand Down Expand Up @@ -148,6 +150,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
}
Expand All @@ -165,6 +171,7 @@ func newConfig(v *viper.Viper, args []string) (*config, error) {
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")
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Usage:
Options:
-b, --bell ring terminal bell changes between updates
-d, --differences highlight changes between updates
-s, --skip-empty-diffs skip snapshots with no changes (+0 -0) in history
-n, --interval <interval> seconds to wait between updates (default "2s")
-p, --precise attempt run command in precise intervals
-c, --clockwork run command in precise intervals forcibly
Expand Down
3 changes: 2 additions & 1 deletion snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ type Snapshot struct {
finish chan<- struct{}
}

//nolint:lll
// NewSnapshot returns Snapshot object.
//
//nolint:lll
func NewSnapshot(id int64, command string, args []string, shell string, shellOpts string, before *Snapshot, finish chan<- struct{}) *Snapshot {
return &Snapshot{
id: id,
Expand Down
185 changes: 94 additions & 91 deletions viddy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"html/template"
"io"
"os"
"sort"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -43,12 +42,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
Expand All @@ -69,6 +68,7 @@ type Viddy struct {
isNoTitle bool
isRingBell bool
isShowDiff bool
skipEmptyDiffs bool
isEditQuery bool
unfold bool
pty bool
Expand Down Expand Up @@ -124,17 +124,20 @@ func NewViddy(conf *config) *Viddy {
snapshots: sync.Map{},
historyRows: map[int64]*HistoryRow{},

historyRowCount: map[int64]int{},

snapshotQueue: snapshotQueue,
queue: make(chan int64),
finishedQueue: make(chan int64),
diffQueue: make(chan int64, 100),

isRingBell: conf.general.bell,
isShowDiff: conf.general.differences,
isNoTitle: conf.general.noTitle,
isDebug: conf.general.debug,
unfold: conf.general.unfold,
pty: conf.general.pty,
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,
Expand All @@ -153,14 +156,14 @@ 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)
}

v.arrange()
Expand All @@ -183,6 +186,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() {
Expand All @@ -203,17 +226,24 @@ 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]
if !ok {
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))
}()
Expand Down Expand Up @@ -249,11 +279,7 @@ 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 {
Expand All @@ -266,34 +292,41 @@ func (v *Viddy) queueHandler() {
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
}
Expand All @@ -305,14 +338,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())
Expand Down Expand Up @@ -628,81 +658,54 @@ func (v *Viddy) Run() error {
return app.Run()
}

func (v *Viddy) goToRow(row int) {
if row < 0 {
row = 0
} else if count := v.historyView.GetRowCount(); row >= count {
row = count - 1
}
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() {
count := v.historyView.GetRowCount()
selection, _ := v.historyView.GetSelection()

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)
}
}
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

[::b]Key Bindings[-:-:-]

[::u]General[-:-:-]
[::u]General[-:-:-]

Toggle time machine mode : [yellow]SPACE[-:-:-]
Toggle suspend execution : [yellow]s[-:-:-]
Expand Down