diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index 4a3a42491..566993a0f 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -162,10 +162,19 @@ func (gui *Gui) clampDiffSelectionToContent(view *gocui.View) { } // clearMainView empties a pane that is being given nothing to show, selection and all. +// +// An emptied pane is showing nothing, so it also goes back to the top and stops +// claiming the render it was showing: whatever it is given next is content the user +// hasn't seen there, and is shown from the top like any other. func (gui *Gui) clearMainView(mainContext types.Context) { - mainContext.GetView().Clear() + view := mainContext.GetView() + view.Clear() + view.SetOrigin(0, 0) mainContext.SetHasSelectableContent(false) gui.State.ContextMgr.UpdateSelectionHighlights() + if manager := gui.getViewBufferManagerForView(view); manager != nil { + manager.ForgetRenderedContent() + } } // updateDiffSelectionVisibility works out whether a main pane holds anything for a diff --git a/pkg/integration/tests/file/pane_shown_again_starts_at_the_top.go b/pkg/integration/tests/file/pane_shown_again_starts_at_the_top.go index 1219543f8..3fc38c584 100644 --- a/pkg/integration/tests/file/pane_shown_again_starts_at_the_top.go +++ b/pkg/integration/tests/file/pane_shown_again_starts_at_the_top.go @@ -56,9 +56,6 @@ var PaneShownAgainStartsAtTheTop = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Secondary(). IsVisible(). Content(Contains("+LINE40")). - /* EXPECTED: OriginY(0) - ACTUAL: */ - OriginYAtLeast(1) }, }) diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 427fd894f..cbabc868f 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -251,6 +251,17 @@ func (self *ViewBufferManager) GetTaskKey() string { return self.taskKey } +// ForgetRenderedContent records that the view no longer shows the render whose key it +// is holding, because it has been emptied. The key says what the view is showing, and +// the next task is compared against it to tell whether that task renders something +// new. A view with nothing in it is showing nothing, so whatever comes next is new. +func (self *ViewBufferManager) ForgetRenderedContent() { + self.taskIDMutex.Lock() + defer self.taskIDMutex.Unlock() + + self.taskKey = "" +} + func NewViewBufferManager( log *logrus.Entry, writer io.Writer, diff --git a/pkg/tasks/tasks_test.go b/pkg/tasks/tasks_test.go index 4aa3a39d7..31c9a09d3 100644 --- a/pkg/tasks/tasks_test.go +++ b/pkg/tasks/tasks_test.go @@ -610,6 +610,50 @@ func TestKeepScrollPositionForNextTask(t *testing.T) { assert.EqualValues(t, 0, beforeStartCount.Load()) } +// A view that has been emptied is showing nothing, so the render it was showing is no +// longer the one to compare the next task against: running the same command again is +// putting content into the view that isn't there any more, and starts from the top. +func TestForgetRenderedContent(t *testing.T) { + resetOrigin, getResetOriginCallCount := getCounter() + + manager := NewViewBufferManager( + utils.NewDummyLog(), + io.Discard, + func() {}, // beforeStart + func() {}, // refreshView + func() {}, // onEndOfInput + resetOrigin, + func() {}, // beginRender + func() {}, // swapInRender + func() gocui.Task { return gocui.NewFakeTask() }, + // no UI thread in the test; run the view mutations inline + func(f func()) error { f(); return nil }, + ) + + runTaskToCompletion := func(key string) { + start := func() (Cmd, io.Reader) { + // not actually starting this because it's not necessary + return ExecCmd{Cmd: exec.Command("blah")}, &BlankLineReader{totalLinesToYield: 3} + } + done := make(chan struct{}) + _ = manager.NewTask( + manager.NewCmdTask(start, "", LinesToRead{100, 50, nil}, func() { close(done) }), key) + <-done + } + + runTaskToCompletion("cmd1") + assert.Equal(t, 1, getResetOriginCallCount()) + + // Rendering the same command's output again leaves the view where it is, that being + // what it already shows. + runTaskToCompletion("cmd1") + assert.Equal(t, 1, getResetOriginCallCount()) + + manager.ForgetRenderedContent() + runTaskToCompletion("cmd1") + assert.Equal(t, 2, getResetOriginCallCount(), "an emptied view is shown its content afresh") +} + func TestNewCmdTaskRefresh(t *testing.T) { type scenario struct { name string