mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Don't run end-of-input handling for a render that was stopped
When a task is stopped to make way for a newer one, stopping closes opts.Stop, and the scanner goroutine then closes lineChan. The read loop's select between those two channels is therefore non-deterministic: it can land on the closed lineChan (ok == false) instead of the opts.Stop case, sending a stopped task into the end-of-input branch. There it runs the full finalize — swapping its half-read off-screen buffer in, applying the saved scroll, clamping the origin to the truncated content, and clearing the loading flag — all of which corrupt what the incoming task is about to render. The most visible symptom is a brief frame of truncated content with the scroll yanked to the top, seen when re-renders overlap rapidly (e.g. the periodic background refresh re-rendering a main view faster than it can load, very easy to hit under LAZYGIT_SLOW_RENDER). The underlying bug predates the off-screen render (the EOF branch always clamped the origin via onEndOfInput), but that change made it far worse by also swapping a truncated buffer into the display. Fix it at the source: in the EOF branch, check whether we were stopped and, if so, bail out like the explicit stop case, leaving the view entirely to the task that replaces us. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
63dac0d77d
commit
0e3d3d3182
|
|
@ -479,10 +479,26 @@ func (self *ViewBufferManager) NewCmdTask(start func() (Cmd, io.Reader), prefix
|
|||
loadingMutex.Unlock()
|
||||
|
||||
if !ok {
|
||||
// We're at EOF before reaching InitialRefreshAfter (the content was
|
||||
// shorter than a screenful), so swap in whatever we read now. Apply
|
||||
// the saved scroll first (if any) so that onEndOfInput clamps it back
|
||||
// into range when the new content turned out shorter than expected.
|
||||
// lineChan is closed. At a genuine end of input we swap in what we
|
||||
// read and finalize. But lineChan is also closed when this task has
|
||||
// been stopped to make way for a newer one: stopping closes
|
||||
// opts.Stop, and the scanner goroutine then closes lineChan, so the
|
||||
// select above can land here instead of on the opts.Stop case. A
|
||||
// stopped task is being replaced and must leave the view to the
|
||||
// incoming task — swapping in its half-read buffer, applying the
|
||||
// saved scroll, clamping the origin, or clearing `loading` would all
|
||||
// corrupt what that task is about to render. So bail out here, the
|
||||
// same as the explicit stop case above.
|
||||
select {
|
||||
case <-opts.Stop:
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
// Genuine end of input: swap in whatever we read (the content was
|
||||
// shorter than a screenful, so we never hit the InitialRefreshAfter
|
||||
// swap). Apply the saved scroll first (if any) so that onEndOfInput
|
||||
// clamps it back into range when the content turned out shorter than
|
||||
// expected.
|
||||
// onEndOfInput reads the view's dimensions (to decide
|
||||
// whether to scroll) and sets the origin, both of which
|
||||
// are UI-thread-only, so run it there.
|
||||
|
|
|
|||
Loading…
Reference in a new issue