From 8eb802d3a090e16026ad0acfa69844f85229e2c1 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 6 Apr 2021 17:00:37 +1000 Subject: [PATCH] fix flicker issue in main view --- go.mod | 2 +- go.sum | 4 ++ pkg/gui/tasks_adapter.go | 14 +++++- pkg/tasks/tasks.go | 14 +++--- vendor/github.com/jesseduffield/gocui/view.go | 43 ++++++++++++++++--- vendor/modules.txt | 2 +- 6 files changed, 65 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 98853adb9..d83bd8e04 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/imdario/mergo v0.3.11 github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4 - github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772 + github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064 github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe // indirect github.com/jesseduffield/yaml v2.1.0+incompatible github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 diff --git a/go.sum b/go.sum index d2037e61e..3efe983fe 100644 --- a/go.sum +++ b/go.sum @@ -102,6 +102,10 @@ github.com/jesseduffield/gocui v0.3.1-0.20210405041826-439abd8b6e07 h1:BymGR28au github.com/jesseduffield/gocui v0.3.1-0.20210405041826-439abd8b6e07/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg= github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772 h1:dg9krj10Udac4IcvlVCOAPktQkfggkgtqRmbDKk7Pzw= github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg= +github.com/jesseduffield/gocui v0.3.1-0.20210406065811-95ef6e13779b h1:3+4+muhhikpls5FePXSRNFgcdoPx8dTdqaCy3AqLz98= +github.com/jesseduffield/gocui v0.3.1-0.20210406065811-95ef6e13779b/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg= +github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064 h1:Oe+QJuUIOd2TU+A3BW5sT1eXqceoBcOOfyoHlGf7F8Y= +github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg= github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E= github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4= github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE= diff --git a/pkg/gui/tasks_adapter.go b/pkg/gui/tasks_adapter.go index bc2fb8a24..d24344b49 100644 --- a/pkg/gui/tasks_adapter.go +++ b/pkg/gui/tasks_adapter.go @@ -83,13 +83,23 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager { gui.Log, view, func() { - view.Clear() + // we could clear here, but that actually has the effect of causing a flicker + // where the view may contain no content momentarily as the gui refreshes. + // Instead, we're rewinding the write pointer so that we will just start + // overwriting the existing content from the top down. Once we've reached + // the end of the content do display, we call view.FlushStaleCells() to + // clear out the remaining content from the previous render. + view.Rewind() }, func() { gui.g.Update(func(*gocui.Gui) error { return nil }) - }) + }, + func() { + view.FlushStaleCells() + }, + ) gui.viewBufferManagerMap[view.Name()] = manager } diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 5854b4f46..8287b3927 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -33,12 +33,13 @@ type ViewBufferManager struct { readLines chan int // beforeStart is the function that is called before starting a new task - beforeStart func() - refreshView func() + beforeStart func() + refreshView func() + flushStaleCells func() } -func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func()) *ViewBufferManager { - return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, readLines: make(chan int, 1024)} +func NewViewBufferManager(log *logrus.Entry, writer io.Writer, beforeStart func(), refreshView func(), flushStaleCells func()) *ViewBufferManager { + return &ViewBufferManager{Log: log, writer: writer, beforeStart: beforeStart, refreshView: refreshView, flushStaleCells: flushStaleCells, readLines: make(chan int, 1024)} } func (m *ViewBufferManager) ReadLines(n int) { @@ -75,7 +76,7 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string loaded := false go utils.Safe(func() { - ticker := time.NewTicker(time.Millisecond * 100) + ticker := time.NewTicker(time.Millisecond * 200) defer ticker.Stop() select { case <-ticker.C: @@ -114,6 +115,9 @@ func (m *ViewBufferManager) NewCmdTask(r io.Reader, cmd *exec.Cmd, prefix string default: } if !ok { + // if we're here then there's nothing left to scan from the source + // so we're at the EOF and can flush the stale content + m.flushStaleCells() m.refreshView() break outer } diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index b13074242..2e4c4c1e3 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -518,16 +518,20 @@ func (v *View) writeCells(x, y int, cells []cell) { // of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must // be called to clear the view's buffer. func (v *View) Write(p []byte) (n int, err error) { - v.tainted = true v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.tainted = true v.makeWriteable(v.wx, v.wy) v.writeRunes(bytes.Runes(p)) - v.writeMutex.Unlock() return len(p), nil } func (v *View) WriteRunes(p []rune) { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + v.tainted = true // Fill with empty cells, if writing outside current view buffer @@ -545,6 +549,8 @@ func (v *View) writeRunes(p []rune) { for _, r := range p { switch r { case '\n': + // clear the rest of the line + v.lines[v.wy] = v.lines[v.wy][0:v.wx] v.wy++ if v.wy >= len(v.lines) { v.lines = append(v.lines, nil) @@ -642,6 +648,15 @@ func (v *View) Read(p []byte) (n int, err error) { // Rewind sets read and write pos to (0, 0). func (v *View) Rewind() { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.rewind() +} + +func (v *View) rewind() { + v.ei.reset() + if err := v.SetReadPos(0, 0); err != nil { // SetReadPos returns error only if x and y are negative // we are passing 0, 0, thus no error should occur. @@ -832,13 +847,31 @@ func (v *View) realPosition(vx, vy int) (x, y int, err error) { // And resets reading and writing offsets. func (v *View) Clear() { v.writeMutex.Lock() - v.Rewind() + defer v.writeMutex.Unlock() + + v.rewind() v.tainted = true - v.ei.reset() v.lines = nil v.viewLines = nil v.clearRunes() - v.writeMutex.Unlock() +} + +// This is for when we've done a rewind for the sake of avoiding a flicker and +// we've reached the end of the new content to display: we need to clear the remaining +// content from the previous round. +func (v *View) FlushStaleCells() { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + // need to wipe the end of the current line and all following lines + if len(v.lines) > 0 && v.wy < len(v.lines) { + // why this needs to be +1 but the 0:v.wx part doesn't, I'm not sure + v.lines = v.lines[0 : v.wy+1] + + if len(v.lines[v.wy]) > 0 && v.wx < len(v.lines[v.wy]) { + v.lines[v.wy] = v.lines[v.wy][0:v.wx] + } + } } // clearRunes erases all the cells in the view. diff --git a/vendor/modules.txt b/vendor/modules.txt index 1bf4922de..f263b6ee6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -149,7 +149,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20210405093708-e79dab8f7772 +# github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064 ## explicit github.com/jesseduffield/gocui # github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe