@@ -27,6 +27,7 @@ enum DiffTokenLayouter {
2727 contentInsets: NSEdgeInsets
2828 ) -> DiffLayout {
2929 let lineHeight = DiffTextLayoutMetrics . lineHeight ( for: style)
30+ let textHeight = ceil ( style. font. ascender - style. font. descender + style. font. leading)
3031 let maxLineWidth = availableWidth > 0 ? availableWidth : . greatestFiniteMagnitude
3132 let lineStartX = contentInsets. left
3233 let maxLineX = lineStartX + maxLineWidth
@@ -37,12 +38,16 @@ enum DiffTokenLayouter {
3738 var maxUsedX = lineStartX
3839 var lineCount = 1
3940 var lineHasContent = false
41+ let lineText = NSMutableString ( )
42+ var lineTextWidth : CGFloat = 0
4043 var previousChangedLexical = false
4144
4245 func moveToNewLine( ) {
4346 lineTop += lineHeight
4447 cursorX = lineStartX
4548 lineHasContent = false
49+ lineText. setString ( " " )
50+ lineTextWidth = 0
4651 previousChangedLexical = false
4752 lineCount += 1
4853 }
@@ -65,9 +70,15 @@ enum DiffTokenLayouter {
6570 }
6671
6772 let attributedText = attributedToken ( for: segment, style: style)
68- let textSize = measuredTextSize ( for: piece. text, font: style. font)
73+ var textMeasurement = measuredIncrementalTextWidth (
74+ for: piece. text,
75+ font: style. font,
76+ lineText: lineText,
77+ lineTextWidth: lineTextWidth
78+ )
79+ var textSize = CGSize ( width: textMeasurement. textWidth, height: textHeight)
6980 let chipInsets = effectiveChipInsets ( for: style)
70- let runWidth = isChangedLexical ? textSize. width + chipInsets. left + chipInsets. right : textSize. width
81+ var runWidth = isChangedLexical ? textSize. width + chipInsets. left + chipInsets. right : textSize. width
7182 let requiredWidth = leadingGap + runWidth
7283
7384 let wrapped = lineHasContent && cursorX + requiredWidth > maxLineX
@@ -79,6 +90,15 @@ enum DiffTokenLayouter {
7990 if piece. tokenKind == . whitespace {
8091 continue
8192 }
93+
94+ textMeasurement = measuredIncrementalTextWidth (
95+ for: piece. text,
96+ font: style. font,
97+ lineText: lineText,
98+ lineTextWidth: lineTextWidth
99+ )
100+ textSize = CGSize ( width: textMeasurement. textWidth, height: textHeight)
101+ runWidth = isChangedLexical ? textSize. width + chipInsets. left + chipInsets. right : textSize. width
82102 }
83103
84104 cursorX += leadingGap
@@ -118,6 +138,7 @@ enum DiffTokenLayouter {
118138 cursorX += runWidth
119139 maxUsedX = max ( maxUsedX, cursorX)
120140 lineHasContent = true
141+ lineTextWidth = textMeasurement. combinedLineWidth
121142 previousChangedLexical = isChangedLexical
122143 }
123144
@@ -146,9 +167,28 @@ enum DiffTokenLayouter {
146167 return NSAttributedString ( string: segment. text, attributes: attributes)
147168 }
148169
149- private static func measuredTextSize( for text: String , font: NSFont ) -> CGSize {
150- let measured = ( text as NSString ) . size ( withAttributes: [ . font: font] )
151- return CGSize ( width: ceil ( measured. width) , height: ceil ( measured. height) )
170+ private static func measuredIncrementalTextWidth(
171+ for text: String ,
172+ font: NSFont ,
173+ lineText: NSMutableString ,
174+ lineTextWidth: CGFloat
175+ ) -> IncrementalTextWidth {
176+ guard !text. isEmpty else {
177+ return IncrementalTextWidth (
178+ textWidth: 0 ,
179+ combinedLineWidth: lineTextWidth
180+ )
181+ }
182+
183+ lineText. append ( text)
184+ // TODO: Fix this later
185+ // This now appends each token to lineText and calls size(withAttributes:) on the entire accumulated line every iteration, which makes layout cost grow quadratically with line length. On long unwrapped diffs (hundreds/thousands of tokens), this is a significant regression from the prior per-token measurement approach and can noticeably slow rendering even though the new performance tests only capture baselines and do not enforce thresholds.
186+ let combinedWidth = lineText. size ( withAttributes: [ . font: font] ) . width
187+ let textWidth = max ( 0 , combinedWidth - lineTextWidth)
188+ return IncrementalTextWidth (
189+ textWidth: textWidth,
190+ combinedLineWidth: combinedWidth
191+ )
152192 }
153193
154194 private static func effectiveChipInsets( for style: TextDiffStyle ) -> NSEdgeInsets {
@@ -262,3 +302,8 @@ private struct LayoutPiece {
262302 let text : String
263303 let isLineBreak : Bool
264304}
305+
306+ private struct IncrementalTextWidth {
307+ let textWidth : CGFloat
308+ let combinedLineWidth : CGFloat
309+ }
0 commit comments