Skip to content

Commit 2622e51

Browse files
committed
Whole Bunch of Fixes and Tests
1 parent 4f2ca59 commit 2622e51

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

Sources/CodeEditTextView/TextLayoutManager/TextAttachments/TextAttachmentManager.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ public final class TextAttachmentManager {
2424
let insertIndex = findInsertionIndex(for: range.location)
2525
orderedAttachments.insert(box, at: insertIndex)
2626
layoutManager?.lineStorage.linesInRange(range).dropFirst().forEach {
27-
layoutManager?.lineStorage.update(atOffset: $0.range.location, delta: 0, deltaHeight: -$0.height)
27+
if $0.height != 0 {
28+
layoutManager?.lineStorage.update(atOffset: $0.range.location, delta: 0, deltaHeight: -$0.height)
29+
}
2830
}
2931
layoutManager?.invalidateLayoutForRange(range)
3032
}

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Iterator.swift

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,23 @@ public extension TextLayoutManager {
102102
for originalPosition: TextLineStorage<TextLine>.TextLinePosition?
103103
) -> (position: TextLineStorage<TextLine>.TextLinePosition, indexRange: ClosedRange<Int>)? {
104104
guard let originalPosition else { return nil }
105+
return determineVisiblePosition(for: (originalPosition, originalPosition.index...originalPosition.index))
106+
}
105107

106-
let attachments = attachments.get(overlapping: originalPosition.range)
108+
func determineVisiblePosition(
109+
for originalPosition: (position: TextLineStorage<TextLine>.TextLinePosition, indexRange: ClosedRange<Int>)
110+
) -> (position: TextLineStorage<TextLine>.TextLinePosition, indexRange: ClosedRange<Int>)? {
111+
let attachments = attachments.get(overlapping: originalPosition.position.range)
107112
guard let firstAttachment = attachments.first, let lastAttachment = attachments.last else {
108113
// No change, either no attachments or attachment doesn't span multiple lines.
109-
return (originalPosition, originalPosition.index...originalPosition.index)
114+
return originalPosition
110115
}
111116

112-
var minIndex = originalPosition.index
113-
var maxIndex = originalPosition.index
114-
var newPosition = originalPosition
117+
var minIndex = originalPosition.indexRange.lowerBound
118+
var maxIndex = originalPosition.indexRange.upperBound
119+
var newPosition = originalPosition.position
115120

116-
if firstAttachment.range.location < originalPosition.range.location,
121+
if firstAttachment.range.location < originalPosition.position.range.location,
117122
let extendedLinePosition = lineStorage.getLine(atOffset: firstAttachment.range.location) {
118123
newPosition = TextLineStorage<TextLine>.TextLinePosition(
119124
data: extendedLinePosition.data,
@@ -125,23 +130,24 @@ public extension TextLayoutManager {
125130
minIndex = min(minIndex, newPosition.index)
126131
}
127132

128-
if lastAttachment.range.max > originalPosition.range.max,
133+
if lastAttachment.range.max > originalPosition.position.range.max,
129134
let extendedLinePosition = lineStorage.getLine(atOffset: lastAttachment.range.max) {
130135
newPosition = TextLineStorage<TextLine>.TextLinePosition(
131136
data: newPosition.data,
132137
range: NSRange(start: newPosition.range.location, end: extendedLinePosition.range.max),
133138
yPos: newPosition.yPos,
134139
height: newPosition.height,
135-
index: newPosition.index
140+
index: newPosition.index // We want to keep the minimum index.
136141
)
137-
maxIndex = max(maxIndex, newPosition.index)
142+
maxIndex = max(maxIndex, extendedLinePosition.index)
138143
}
139144

140-
if newPosition == originalPosition {
145+
// Base case, we haven't updated anything
146+
if minIndex...maxIndex == originalPosition.indexRange {
141147
return (newPosition, minIndex...maxIndex)
142148
} else {
143149
// Recurse, to make sure we combine all necessary lines.
144-
return determineVisiblePosition(for: newPosition)
150+
return determineVisiblePosition(for: (newPosition, minIndex...maxIndex))
145151
}
146152
}
147153
}

Tests/CodeEditTextViewTests/LayoutManager/TextLayoutManagerAttachmentsTests.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,95 @@ struct TextLayoutManagerAttachmentsTests {
1717
let layoutManager: TextLayoutManager
1818

1919
init() throws {
20-
textView = TextView(string: "A\nB\nC\nD")
20+
textView = TextView(string: "12\n45\n78\n01\n")
2121
textView.frame = NSRect(x: 0, y: 0, width: 1000, height: 1000)
2222
textStorage = textView.textStorage
2323
layoutManager = try #require(textView.layoutManager)
2424
}
2525

26+
@Test
27+
func addAndGetAttachments() throws {
28+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 8))
29+
#expect(layoutManager.attachments.get(overlapping: textView.documentRange).count == 1)
30+
#expect(layoutManager.attachments.get(overlapping: NSRange(start: 0, end: 3)).count == 1)
31+
#expect(layoutManager.attachments.get(startingIn: NSRange(start: 0, end: 3)).count == 1)
32+
}
33+
2634
// MARK: - Determine Visible Line Tests
2735

2836
@Test
29-
func determineVisibleLinesMovesForwards() {
30-
layoutManager.attachments.attachments(overlapping: <#T##NSRange#>)
37+
func determineVisibleLinesMovesForwards() throws {
38+
// From middle of the first line, to middle of the third line
39+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 8))
40+
41+
// Start with the first line, should extend to the third line
42+
let originalPosition = try #require(layoutManager.lineStorage.getLine(atIndex: 0)) // zero-indexed
43+
let newPosition = try #require(layoutManager.determineVisiblePosition(for: originalPosition))
44+
45+
#expect(newPosition.indexRange == 0...2)
46+
#expect(newPosition.position.range == NSRange(start: 0, end: 9)) // Lines one -> three
47+
}
48+
49+
@Test
50+
func determineVisibleLinesMovesBackwards() throws {
51+
// From middle of the first line, to middle of the third line
52+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 8))
53+
54+
// Start with the third line, should extend back to the first line
55+
let originalPosition = try #require(layoutManager.lineStorage.getLine(atIndex: 2)) // zero-indexed
56+
let newPosition = try #require(layoutManager.determineVisiblePosition(for: originalPosition))
57+
58+
#expect(newPosition.indexRange == 0...2)
59+
#expect(newPosition.position.range == NSRange(start: 0, end: 9)) // Lines one -> three
3160
}
3261

3362
@Test
34-
func determineVisibleLinesMovesBackwards() {
63+
func determineVisibleLinesMergesMultipleAttachments() throws {
64+
// Two attachments, meeting at the third line. `determineVisiblePosition` should merge all four lines.
65+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 7))
66+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 7, end: 11))
67+
68+
let originalPosition = try #require(layoutManager.lineStorage.getLine(atIndex: 2)) // zero-indexed
69+
let newPosition = try #require(layoutManager.determineVisiblePosition(for: originalPosition))
3570

71+
#expect(newPosition.indexRange == 0...3)
72+
#expect(newPosition.position.range == NSRange(start: 0, end: 12)) // Lines one -> four
3673
}
3774

3875
@Test
39-
func determineVisibleLinesMergesMultipleAttachments() {
76+
func determineVisibleLinesMergesOverlappingAttachments() throws {
77+
// Two attachments, overlapping at the third line. `determineVisiblePosition` should merge all four lines.
78+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 7))
79+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 5, end: 11))
4080

81+
let originalPosition = try #require(layoutManager.lineStorage.getLine(atIndex: 2)) // zero-indexed
82+
let newPosition = try #require(layoutManager.determineVisiblePosition(for: originalPosition))
83+
84+
#expect(newPosition.indexRange == 0...3)
85+
#expect(newPosition.position.range == NSRange(start: 0, end: 12)) // Lines one -> four
4186
}
4287

4388
// MARK: - Iterator Tests
4489

4590
@Test
4691
func iterateWithAttachments() {
92+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 1, end: 2))
93+
94+
let lines = layoutManager.linesStartingAt(0, until: 1000)
4795

96+
// Line "5" is from the trailing newline. That shows up as an empty line in the view.
97+
#expect(lines.map { $0.index } == [0, 1, 2, 3, 4])
4898
}
4999

50100
@Test
51101
func iterateWithMultilineAttachments() {
102+
// Two attachments, meeting at the third line.
103+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 2, end: 7))
104+
layoutManager.attachments.add(DemoTextAttachment(), for: NSRange(start: 7, end: 11))
105+
106+
let lines = layoutManager.linesStartingAt(0, until: 1000)
52107

108+
// Line "5" is from the trailing newline. That shows up as an empty line in the view.
109+
#expect(lines.map { $0.index } == [0, 4])
53110
}
54111
}

0 commit comments

Comments
 (0)