Revert "Stop mapping stale-tail view lines to the wrong buffer line"

This reverts the freshViewLineCount stale-tail guard (§8), restoring the
demonstrate-the-bug test state. The upcoming off-screen render rebuilds
the displayed buffer wholesale on swap and lets refreshViewLinesIfNeeded
truncate, so the stale tail never forms — a cleaner fix than tracking a
fresh-count. Removing the guard on its own keeps that change focused; the
bug it guarded against is re-fixed by the truncation in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-06-09 11:22:55 +02:00
parent 45af0aac30
commit 34566357f4
2 changed files with 13 additions and 18 deletions

View file

@ -100,16 +100,6 @@ type View struct {
// true and viewLines to nil
viewLines []viewLine
// Number of leading entries in viewLines that correspond to the current
// buffer (v.buf.lines), as built by the most recent refreshViewLinesIfNeeded.
// refreshViewLinesIfNeeded overwrites viewLines in place without truncating,
// so when a re-render is shorter than the previous one the tail keeps stale
// entries from that previous render (drawn deliberately, to avoid flicker
// while the new content loads). Those entries don't correspond to the
// current buffer, so readers that map a view line to a buffer line must
// ignore them; this is the count of the entries they may trust.
freshViewLineCount int
// writeMutex protects locks the write process
writeMutex sync.Mutex
@ -240,7 +230,6 @@ type pos struct {
func (v *View) clearViewLines() {
v.tainted = true
v.viewLines = nil
v.freshViewLineCount = 0
v.clearHover()
}
@ -1513,7 +1502,6 @@ func (v *View) refreshViewLinesIfNeeded() {
}
v.firstDirtyLine = len(lines)
v.freshViewLineCount = lineIdx
v.tainted = false
}
@ -1754,15 +1742,19 @@ func (v *View) BufferLineForViewLine(y int) (int, bool) {
func (v *View) bufferLineForViewLine(y int) (int, bool) {
v.refreshViewLinesIfNeeded()
// Bound on freshViewLineCount rather than len(v.viewLines): the entries past
// it are a stale tail retained for flicker-avoidance (see freshViewLineCount)
// and don't correspond to the current buffer. Within the fresh range every
// entry's linesY was just built from v.buf.lines, so it is guaranteed in range.
if y < 0 || y >= v.freshViewLineCount {
if y < 0 || y >= len(v.viewLines) {
return 0, false
}
return v.viewLines[y].linesY, true
// refreshViewLinesIfNeeded overwrites viewLines in place without truncating,
// so while a shorter re-render is loading, the tail of viewLines can still
// hold stale entries pointing past the (shrunk) v.buf.lines. Guard against that.
linesY := v.viewLines[y].linesY
if linesY >= len(v.buf.lines) {
return 0, false
}
return linesY, true
}
// indexFunc allows to split lines by words taking into account spaces

View file

@ -230,7 +230,10 @@ func TestBufferLineForViewLineStaleTail(t *testing.T) {
// buffer, so the mapping must fail. (On the buggy code it instead maps to
// buffer line 1, the stale entry's lingering index.)
_, ok = v.BufferLineForViewLine(4)
/* EXPECTED:
assert.False(t, ok)
ACTUAL: */
assert.True(t, ok)
}
func TestContainsColoredText(t *testing.T) {