mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-28 02:24:49 -05:00
Don't share a live view's buffer when copying its content
moveMainContextToTop copies the current top view's content into the view
it's promoting, to avoid a flicker. The source can be a main view with a
live streaming task (e.g. resolving a conflict promotes the merge-conflicts
view over a main view that's mid-diff), and CopyContent both read and
published that source's buffer unsafely:
- it read the source's lines/viewLines while locking only the
destination, racing the task's concurrent Write; and
- it aliased the source's row slices into the destination, so the
source's ongoing appends (growslice reading the shared array) and
refreshViewLinesIfNeeded's in-place wrapping-cache writes (&lines[i])
kept racing this view's rendering after the copy.
Lock the source for the read, and shallow-clone the row slices so the
destination gets its own arrays. The per-row cell data is immutable once
written, so it stays shared -- the clone cost is proportional to the
number of rows, not their contents.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
d48c8174d5
commit
1efcfcc148
+18
-2
@@ -7,6 +7,7 @@ package gocui
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
@@ -1146,10 +1147,25 @@ func (v *View) CopyContent(from *View) {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
// A background task may be streaming output into the source view's buffer
|
||||
// via Write, so read it under its own lock. The source is always a
|
||||
// different view than the destination (see the sole caller,
|
||||
// moveMainContextToTop), and no other code holds two view write locks at
|
||||
// once, so this can't deadlock.
|
||||
from.writeMutex.Lock()
|
||||
defer from.writeMutex.Unlock()
|
||||
|
||||
v.clear()
|
||||
|
||||
v.lines = from.lines
|
||||
v.viewLines = from.viewLines
|
||||
// Clone the row slices rather than sharing them: the source view stays
|
||||
// live (its streaming task keeps appending rows, and refreshViewLinesIfNeeded
|
||||
// fills each row's wrapping cache in place via &lines[i]), so sharing the
|
||||
// backing arrays would race those writes against this view's own rendering.
|
||||
// This is a shallow clone -- the per-row cell data is immutable once written
|
||||
// and stays shared, so the cost is proportional to the number of rows, not
|
||||
// their contents.
|
||||
v.lines = slices.Clone(from.lines)
|
||||
v.viewLines = slices.Clone(from.viewLines)
|
||||
v.ox = from.ox
|
||||
v.oy = from.oy
|
||||
v.cx = from.cx
|
||||
|
||||
Reference in New Issue
Block a user