mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Stop mapping stale-tail view lines to the wrong buffer line
Track how many leading viewLines entries the most recent refresh built from the current buffer (freshViewLineCount) and bound the view-line→ buffer-line mapping on it. The entries past that count are the stale tail refreshViewLinesIfNeeded leaves in place for flicker-avoidance; they belong to a previous, longer render and must not be mapped. The old in-range guard was insufficient: with wrapping a stale entry's buffer index can still be in range of the shrunk buffer. Within the fresh range every entry was just built from the current buffer, so its index is guaranteed in range and the guard is no longer needed. This is the §8 correctness fix the identity-based escape restore depends on, since that read scans the view buffer while it is still loading. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
93286c6a93
commit
e3214a34dd
|
|
@ -74,6 +74,16 @@ type View struct {
|
|||
// true and viewLines to nil
|
||||
viewLines []viewLine
|
||||
|
||||
// Number of leading entries in viewLines that correspond to the current
|
||||
// buffer (v.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
|
||||
|
||||
// If the last character written was a newline, we don't write it but
|
||||
// instead set pendingNewline to true. If more text is written, we write the
|
||||
// newline then. This is to avoid having an extra blank at the end of the view.
|
||||
|
|
@ -212,6 +222,7 @@ type pos struct {
|
|||
func (v *View) clearViewLines() {
|
||||
v.tainted = true
|
||||
v.viewLines = nil
|
||||
v.freshViewLineCount = 0
|
||||
v.clearHover()
|
||||
}
|
||||
|
||||
|
|
@ -1476,6 +1487,7 @@ func (v *View) refreshViewLinesIfNeeded() {
|
|||
}
|
||||
|
||||
v.firstDirtyLine = len(lines)
|
||||
v.freshViewLineCount = lineIdx
|
||||
v.tainted = false
|
||||
}
|
||||
|
||||
|
|
@ -1716,19 +1728,15 @@ func (v *View) BufferLineForViewLine(y int) (int, bool) {
|
|||
func (v *View) bufferLineForViewLine(y int) (int, bool) {
|
||||
v.refreshViewLinesIfNeeded()
|
||||
|
||||
if y < 0 || y >= len(v.viewLines) {
|
||||
// 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.lines, so it is guaranteed in range.
|
||||
if y < 0 || y >= v.freshViewLineCount {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// 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.lines. Guard against that.
|
||||
linesY := v.viewLines[y].linesY
|
||||
if linesY >= len(v.lines) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return linesY, true
|
||||
return v.viewLines[y].linesY, true
|
||||
}
|
||||
|
||||
// indexFunc allows to split lines by words taking into account spaces
|
||||
|
|
|
|||
|
|
@ -230,10 +230,7 @@ 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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue