Skip to content

Commit 29844db

Browse files
committed
Fix viewport blanking after large programmatic scroll (setFrameSize re-entrance)
When `relocateViewport(to: docStart)` calls `setFrameSize(width, lineHeight)` from a scrolled-down position, AppKit must synchronously retract the clip view to fit the new bounds. That fires `prepareContent` from inside `super.setFrameSize`, which calls `updateContentSizeIfNeeded` and re-enters `setFrameSize` with the full document height. The recursive call correctly resizes `contentView`. When control returns to the outer call, `newSize` is still the original (small) value, and `contentView.frame.size = newSize` stomps the recursive call's result. End state: textView at full height, contentView and contentViewportView at one-line height, fragment views past line 1 clipped. Manifests as Cmd-End → Cmd-Home blanking the editor on a multi-screen word-wrapped document. Resizing the window or toggling word-wrap restores rendering because both go through a fresh non-re-entrant `setFrameSize`. Fix: after `super.setFrameSize`, read `frame.size` (which reflects any recursive resize) rather than the stale `newSize` parameter. When no re-entrance occurred, `frame.size == newSize`, so the common path is unchanged.
1 parent c36bf60 commit 29844db

1 file changed

Lines changed: 9 additions & 2 deletions

File tree

Sources/STTextViewAppKit/STTextView.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,11 +1465,18 @@ open class STTextView: NSView, NSTextInput, NSTextContent, STTextViewProtocol {
14651465
override open func setFrameSize(_ newSize: NSSize) {
14661466
super.setFrameSize(newSize)
14671467

1468+
// `super.setFrameSize` can synchronously fire `prepareContent`, which
1469+
// may recursively call back into `setFrameSize` with a different
1470+
// size. In that case `self.frame.size` no longer matches `newSize`;
1471+
// use the current frame so we don't stomp the recursive call's
1472+
// result and leave `contentView` pinned to the intermediate size.
1473+
let effectiveSize = frame.size
1474+
14681475
// contentView should always fill the entire STTextView
14691476
contentView.frame.origin.x = gutterView?.frame.width ?? 0
1470-
contentView.frame.size = newSize
1477+
contentView.frame.size = effectiveSize
14711478

1472-
updateTextContainerSize(proposedSize: newSize)
1479+
updateTextContainerSize(proposedSize: effectiveSize)
14731480

14741481
if inLayout {
14751482
needsRelayout = true

0 commit comments

Comments
 (0)