@@ -9,22 +9,32 @@ public final class NSTextDiffView: NSView {
99 /// Setting this value updates rendered diff output when content changes.
1010 public var original : String {
1111 didSet {
12- updateSegmentsIfNeeded ( )
12+ guard !isBatchUpdating else {
13+ return
14+ }
15+ _ = updateSegmentsIfNeeded ( )
1316 }
1417 }
1518
1619 /// The source text after edits.
1720 /// Setting this value updates rendered diff output when content changes.
1821 public var updated : String {
1922 didSet {
20- updateSegmentsIfNeeded ( )
23+ guard !isBatchUpdating else {
24+ return
25+ }
26+ _ = updateSegmentsIfNeeded ( )
2127 }
2228 }
2329
2430 /// Visual style used to render additions, deletions, and unchanged text.
2531 /// Setting this value redraws the view without recomputing diff segments.
2632 public var style : TextDiffStyle {
2733 didSet {
34+ guard !isBatchUpdating else {
35+ pendingStyleInvalidation = true
36+ return
37+ }
2838 invalidateCachedLayout ( )
2939 }
3040 }
@@ -33,7 +43,10 @@ public final class NSTextDiffView: NSView {
3343 /// Setting this value updates rendered diff output when mode changes.
3444 public var mode : TextDiffComparisonMode {
3545 didSet {
36- updateSegmentsIfNeeded ( )
46+ guard !isBatchUpdating else {
47+ return
48+ }
49+ _ = updateSegmentsIfNeeded ( )
3750 }
3851 }
3952
@@ -43,6 +56,8 @@ public final class NSTextDiffView: NSView {
4356 private var lastOriginal : String
4457 private var lastUpdated : String
4558 private var lastModeKey : Int
59+ private var isBatchUpdating = false
60+ private var pendingStyleInvalidation = false
4661
4762 private var cachedWidth : CGFloat = - 1
4863 private var cachedLayout : DiffLayout ?
@@ -83,6 +98,7 @@ public final class NSTextDiffView: NSView {
8398 super. init ( frame: . zero)
8499 }
85100
101+ #if TESTING
86102 init (
87103 original: String ,
88104 updated: String ,
@@ -101,6 +117,7 @@ public final class NSTextDiffView: NSView {
101117 self . segments = diffProvider ( original, updated, mode)
102118 super. init ( frame: . zero)
103119 }
120+ #endif
104121
105122 @available ( * , unavailable, message: " Use init(original:updated:style:mode:) " )
106123 required init ? ( coder: NSCoder ) {
@@ -133,17 +150,44 @@ public final class NSTextDiffView: NSView {
133150 }
134151 }
135152
136- private func updateSegmentsIfNeeded( ) {
153+ /// Atomically updates view inputs and recomputes diff segments at most once.
154+ public func setContent(
155+ original: String ,
156+ updated: String ,
157+ style: TextDiffStyle ,
158+ mode: TextDiffComparisonMode
159+ ) {
160+ isBatchUpdating = true
161+ defer {
162+ isBatchUpdating = false
163+ let needsStyleInvalidation = pendingStyleInvalidation
164+ pendingStyleInvalidation = false
165+
166+ let didRecompute = updateSegmentsIfNeeded ( )
167+ if needsStyleInvalidation, !didRecompute {
168+ invalidateCachedLayout ( )
169+ }
170+ }
171+
172+ self . style = style
173+ self . mode = mode
174+ self . original = original
175+ self . updated = updated
176+ }
177+
178+ @discardableResult
179+ private func updateSegmentsIfNeeded( ) -> Bool {
137180 let newModeKey = Self . modeKey ( for: mode)
138181 guard original != lastOriginal || updated != lastUpdated || newModeKey != lastModeKey else {
139- return
182+ return false
140183 }
141184
142185 lastOriginal = original
143186 lastUpdated = updated
144187 lastModeKey = newModeKey
145188 segments = diffProvider ( original, updated, mode)
146189 invalidateCachedLayout ( )
190+ return true
147191 }
148192
149193 private func layoutForCurrentWidth( ) -> DiffLayout {
0 commit comments