Skip to content

Commit c739269

Browse files
committed
Merge branch 'fix-slow-scroll-down'
Fixes: #471
2 parents 71861ba + 989173a commit c739269

4 files changed

Lines changed: 112 additions & 7 deletions

File tree

‎internal/pager.go‎

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ type Pager struct {
6868
// Written and read only by the main loop, like quit above, so no locking.
6969
sawUserInput bool
7070

71+
// Set when the user scrolls down. On the next redraw, we start following
72+
// the end of the input if the user scrolled all the way there.
73+
//
74+
// Written and read only by the main loop, like quit above, so no locking.
75+
pendingFollowCheck bool
76+
7177
scrollPosition scrollPosition
7278

7379
// How far right we have scrolled horizontally. The unit is visual screen
@@ -469,13 +475,28 @@ func (p *Pager) handleScrolledUp() {
469475
p.setTargetLine(nil)
470476
}
471477

478+
// Call after scrolling down. Cheap. If we ended up at the end of the input,
479+
// following it starts on the next redraw.
472480
func (p *Pager) handleScrolledDown() {
473-
if p.isScrolledToEnd() {
474-
// Follow output
475-
p.setTargetLine(ptr.To(linemetadata.IndexMax()))
476-
} else {
477-
p.setTargetLine(nil)
481+
p.setTargetLine(nil)
482+
483+
// After setTargetLine(), which clears it
484+
p.pendingFollowCheck = true
485+
}
486+
487+
// Start following the end of the input if the user scrolled down to it since
488+
// the last call. Expensive when a check is pending, cheap otherwise.
489+
func (p *Pager) followIfScrolledToEnd() {
490+
if !p.pendingFollowCheck {
491+
return
492+
}
493+
p.pendingFollowCheck = false
494+
495+
if !p.isScrolledToEnd() {
496+
return
478497
}
498+
499+
p.setTargetLine(ptr.To(linemetadata.IndexMax()))
479500
}
480501

481502
func (p *Pager) handleMoreLinesAvailable() {
@@ -513,6 +534,11 @@ func (p *Pager) setTargetLine(targetLine *linemetadata.Index) {
513534

514535
log.Trace("Pager: Setting target line to ", targetLine, "...")
515536
p.TargetLine = targetLine
537+
538+
// An explicit target wins over any not-yet-done follow check from an
539+
// earlier scroll down
540+
p.pendingFollowCheck = false
541+
516542
if targetLine == nil {
517543
// No target, just do your thing
518544
r.SetPauseAfterLines(reader.DEFAULT_PAUSE_AFTER_LINES)

‎internal/pagermode-viewing_test.go‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"os"
55
"testing"
66

7+
"github.com/walles/moor/v2/internal/linemetadata"
78
"github.com/walles/moor/v2/internal/reader"
89
"github.com/walles/twin"
910
"gotest.tools/v3/assert"
@@ -35,6 +36,83 @@ func TestErrUnlessExecutable_no(t *testing.T) {
3536
}
3637
}
3738

39+
// Counts how many times lines are fetched from the wrapped reader
40+
type lineReadCountingReader struct {
41+
reader.Reader
42+
lineReads int
43+
}
44+
45+
func (r *lineReadCountingReader) GetLine(index linemetadata.Index) *reader.NumberedLine {
46+
r.lineReads++
47+
return r.Reader.GetLine(index)
48+
}
49+
50+
func (r *lineReadCountingReader) GetLines(firstLine linemetadata.Index, wantedLineCount int) reader.InputLines {
51+
r.lineReads++
52+
return r.Reader.GetLines(firstLine, wantedLineCount)
53+
}
54+
55+
func (r *lineReadCountingReader) GetLinesPreallocated(firstLine linemetadata.Index, resultLines *[]reader.NumberedLine) reader.Status {
56+
r.lineReads++
57+
return r.Reader.GetLinesPreallocated(firstLine, resultLines)
58+
}
59+
60+
// Inertial scrolling can queue hundreds of scroll-down events, and the screen
61+
// is redrawn only after all of them have been handled. So handling one must not
62+
// touch the input lines, all such work waits for the next redraw.
63+
func TestViewingScrollDownIsCheap(t *testing.T) {
64+
r := reader.NewFromTextForTesting("test", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n")
65+
66+
pager := NewPager(r)
67+
pager.screen = twin.NewFakeScreen(20, 5)
68+
countingReader := &lineReadCountingReader{Reader: r}
69+
pager.filteringReader.BackingReader = countingReader
70+
71+
mode := PagerModeViewing{pager: pager}
72+
mode.onKey(twin.KeyDown)
73+
74+
assert.Equal(t, 0, countingReader.lineReads)
75+
}
76+
77+
// Scrolling down to the last line starts following the end of the input.
78+
func TestViewingScrollDownToEndFollows(t *testing.T) {
79+
r := reader.NewFromTextForTesting("test", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n")
80+
81+
pager := NewPager(r)
82+
pager.screen = twin.NewFakeScreen(20, 5)
83+
assert.Assert(t, pager.TargetLine == nil)
84+
85+
// One line per press is guaranteed to get us to the end
86+
mode := PagerModeViewing{pager: pager}
87+
for range r.GetLineCount() {
88+
mode.onKey(twin.KeyDown)
89+
}
90+
pager.redraw("")
91+
92+
assert.Assert(t, pager.TargetLine != nil)
93+
assert.Equal(t, linemetadata.IndexMax(), *pager.TargetLine)
94+
}
95+
96+
// Choosing a target line after scrolling down to the end keeps that target.
97+
func TestViewingScrollDownToEndThenTargetLine(t *testing.T) {
98+
r := reader.NewFromTextForTesting("test", "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n")
99+
100+
pager := NewPager(r)
101+
pager.screen = twin.NewFakeScreen(20, 5)
102+
103+
// One line per press is guaranteed to get us to the end
104+
mode := PagerModeViewing{pager: pager}
105+
for range r.GetLineCount() {
106+
mode.onKey(twin.KeyDown)
107+
}
108+
target := linemetadata.IndexFromOneBased(2)
109+
pager.setTargetLine(&target)
110+
pager.redraw("")
111+
112+
assert.Assert(t, pager.TargetLine != nil)
113+
assert.Equal(t, target, *pager.TargetLine)
114+
}
115+
38116
func TestViewingFooter_WithSpinner(t *testing.T) {
39117
r := reader.NewFromTextForTesting("", "text")
40118

‎internal/screenLines.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type renderedScreen struct {
4343
// Refresh the whole pager display, both contents lines and the status line at
4444
// the bottom, and show the result on the terminal
4545
func (p *Pager) redraw(spinner string) {
46+
p.followIfScrolledToEnd()
4647
p.renderIntoCells(spinner)
4748
p.screen.Show()
4849
}

‎internal/scrollPosition.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ func (p *Pager) scrollToEnd() {
380380
}
381381
}
382382

383-
// Can be either because Pager.scrollToEnd() was just called or because the user
384-
// has pressed the down arrow enough times.
383+
// True if the end of the input is visible on screen. Expensive, don't call too
384+
// often.
385385
func (p *Pager) isScrolledToEnd() bool {
386386
inputLineCount := p.Reader().GetLineCount()
387387
if inputLineCount == 0 {

0 commit comments

Comments
 (0)