Skip to content

Commit 19abc0c

Browse files
afollestadcodex
andcommitted
Preserve inline code backgrounds after wrapping
Draw inline code fills from glyph positions because TextKit can report zero-width bounds after hidden delimiters wrap. Co-authored-by: Codex <codex@openai.com>
1 parent 73de5e5 commit 19abc0c

7 files changed

Lines changed: 187 additions & 31 deletions
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import AppKit
2+
3+
extension BlockInputTextView {
4+
/// TextKit can report zero-width bounds and omit native backgrounds when hidden delimiters wrap.
5+
/// Keep valid bounds for bidirectional runs; glyph positions recover the empty rectangles.
6+
func drawInlineCodeBackgrounds(in dirtyRect: NSRect) {
7+
guard let textStorage, let layoutManager, let textContainer else {
8+
return
9+
}
10+
layoutManager.ensureLayout(for: textContainer)
11+
let origin = textContainerOrigin
12+
let fullRange = NSRange(location: 0, length: textStorage.length)
13+
textStorage.enumerateAttribute(.blockInputInlineCodeBackground, in: fullRange) { value, range, _ in
14+
guard let color = value as? NSColor else {
15+
return
16+
}
17+
let glyphRange = layoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
18+
color.setFill()
19+
var glyphIndex = glyphRange.location
20+
while glyphIndex < NSMaxRange(glyphRange) {
21+
var lineRange = NSRange()
22+
let lineRect = layoutManager.blockInputGlyphLineRect(forGlyphAt: glyphIndex, effectiveRange: &lineRange)
23+
let intersection = NSIntersectionRange(glyphRange, lineRange)
24+
var rect = layoutManager.boundingRect(forGlyphRange: intersection, in: textContainer)
25+
if rect.width <= 0 {
26+
let fragmentRect = layoutManager.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: nil)
27+
let startX = fragmentRect.minX + layoutManager.location(forGlyphAt: intersection.location).x
28+
let endIndex = NSMaxRange(intersection)
29+
let endX = endIndex < NSMaxRange(lineRange)
30+
? fragmentRect.minX + layoutManager.location(forGlyphAt: endIndex).x
31+
: lineRect.maxX
32+
rect.origin.x = min(startX, endX)
33+
rect.size.width = abs(endX - startX)
34+
}
35+
rect.origin.y = lineRect.minY
36+
rect.size.height = lineRect.height
37+
rect = rect.offsetBy(dx: origin.x, dy: origin.y)
38+
if rect.intersects(dirtyRect) {
39+
rect.fill()
40+
}
41+
glyphIndex = max(glyphIndex + 1, NSMaxRange(lineRange))
42+
}
43+
}
44+
}
45+
}
46+
47+
extension NSAttributedString.Key {
48+
/// Keeps inline code fills out of TextKit's unreliable native background drawing around hidden delimiters.
49+
static let blockInputInlineCodeBackground = NSAttributedString.Key("BlockInputInlineCodeBackground")
50+
}

‎Sources/BlockInputKit/AppKit/BlockInputTextView+Links.swift‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct BlockInputLinkHitResult {
1313
extension BlockInputTextView {
1414
override func draw(_ dirtyRect: NSRect) {
1515
drawInlineChipBackgrounds(in: dirtyRect)
16+
drawInlineCodeBackgrounds(in: dirtyRect)
1617
super.draw(dirtyRect)
1718
// Images paint after super so selection highlight cannot wash them out.
1819
drawInlineImages(in: dirtyRect)

‎Sources/BlockInputKit/AppKit/BlockItem/BlockInputBlockItem+Formatting.swift‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ extension BlockInputBlockItem {
286286
textStorage.removeAttribute(.blockInputInlineImage, range: fullRange)
287287
textStorage.removeAttribute(.blockInputHiddenDelimiter, range: fullRange)
288288
textStorage.removeAttribute(.blockInputInlineCode, range: fullRange)
289+
textStorage.removeAttribute(.blockInputInlineCodeBackground, range: fullRange)
289290
textStorage.removeAttribute(.paragraphStyle, range: fullRange)
290291
applyBaseParagraphStyle(for: block, textStorage: textStorage, range: fullRange)
291292
applyCodeBlockAttributes(for: block, textStorage: textStorage)
@@ -314,7 +315,7 @@ extension BlockInputBlockItem {
314315
if isInlineCodeSelection {
315316
attributes[.font] = inlineCodeFont(for: font)
316317
attributes[.foregroundColor] = inlineCodeForegroundColor()
317-
attributes[.backgroundColor] = inlineCodeBackgroundColor()
318+
attributes[.blockInputInlineCodeBackground] = inlineCodeBackgroundColor()
318319
attributes[.blockInputInlineCode] = true
319320
} else {
320321
attributes = Self.applyingInlineMarkdownStyles(
@@ -354,7 +355,7 @@ extension BlockInputBlockItem {
354355
for key: NSAttributedString.Key in [
355356
.foregroundColor, .backgroundColor, .underlineStyle, .strikethroughStyle, .link, .toolTip, .kern,
356357
.baselineOffset, .blockInputInlineChip, .blockInputInlineImage, .blockInputHiddenDelimiter,
357-
.blockInputInlineCode
358+
.blockInputInlineCode, .blockInputInlineCodeBackground
358359
] {
359360
attributes.removeValue(forKey: key)
360361
}

‎Sources/BlockInputKit/AppKit/BlockItem/BlockInputBlockItem+InlineCode.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension BlockInputBlockItem {
2222
[
2323
.font: inlineFont,
2424
.foregroundColor: foregroundColor,
25-
.backgroundColor: backgroundColor
25+
.blockInputInlineCodeBackground: backgroundColor
2626
],
2727
range: contentRange
2828
)

0 commit comments

Comments
 (0)