Skip to content

Commit a534d12

Browse files
authored
Fix viewport blanking after large programmatic scroll (setFrameSize re-entrance) (#110)
## Summary Cmd-End → Cmd-Home on a document taller than one viewport leaves the editor blank except for the first wrapped fragment. Viewport layout is correct; rendering is clipped because `contentView` and `contentViewportView` end up pinned to ~one-line tall while the textView itself is at full document height. ## Root cause 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. ## Repro Open a multi-screen word-wrapped document, Cmd-End, then Cmd-Home. The editor renders only the first wrapped fragment of paragraph 1. Resizing the window or toggling word-wrap restores rendering because both go through a fresh non-re-entrant `setFrameSize`. Verified in the bundled \`TextEdit.SwiftUI\` example with a ~24KB document; reverting the change reproduces the blanking. ## 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 a534d12

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)