@@ -32,11 +32,20 @@ public extension TextLayoutManager {
3232 func linesStartingAt( _ minY: CGFloat , until maxY: CGFloat ) -> YPositionIterator {
3333 YPositionIterator ( minY: minY, maxY: maxY, layoutManager: self )
3434 }
35-
35+ /// Iterate over all lines that overlap a document range.
36+ /// - Parameters:
37+ /// - range: The range in the document to iterate over.
38+ /// - Returns: An iterator for lines in the range. The iterator returns lines that *overlap* with the range.
39+ /// Returned lines may extend slightly before or after the queried range.
3640 func linesInRange( _ range: NSRange ) -> RangeIterator {
3741 RangeIterator ( range: range, layoutManager: self )
3842 }
3943
44+ /// This iterator iterates over "visible" text positions that overlap a range of vertical `y` positions
45+ /// using ``TextLayoutManager/determineVisiblePosition(for:)``.
46+ ///
47+ /// Next elements are retrieved lazily. Additionally, this iterator uses a stable `index` rather than a y position
48+ /// or a range to fetch the next line. This means the line storage can be updated during iteration.
4049 struct YPositionIterator : LazySequenceProtocol , IteratorProtocol {
4150 typealias TextLinePosition = TextLineStorage < TextLine > . TextLinePosition
4251
@@ -72,6 +81,11 @@ public extension TextLayoutManager {
7281 }
7382 }
7483
84+ /// This iterator iterates over "visible" text positions that overlap a document using
85+ /// ``TextLayoutManager/determineVisiblePosition(for:)``.
86+ ///
87+ /// Next elements are retrieved lazily. Additionally, this iterator uses a stable `index` rather than a y position
88+ /// or a range to fetch the next line. This means the line storage can be updated during iteration.
7589 struct RangeIterator : LazySequenceProtocol , IteratorProtocol {
7690 typealias TextLinePosition = TextLineStorage < TextLine > . TextLinePosition
7791
@@ -139,12 +153,25 @@ public extension TextLayoutManager {
139153 for originalPosition: TextLineStorage < TextLine > . TextLinePosition ?
140154 ) -> ( position: TextLineStorage < TextLine > . TextLinePosition , indexRange: ClosedRange < Int > ) ? {
141155 guard let originalPosition else { return nil }
142- return determineVisiblePosition ( for: ( originalPosition, originalPosition. index... originalPosition. index) )
156+ return determineVisiblePositionRecursively (
157+ for: ( originalPosition, originalPosition. index... originalPosition. index) ,
158+ recursionDepth: 0
159+ )
143160 }
144161
145- func determineVisiblePosition(
146- for originalPosition: ( position: TextLineStorage < TextLine > . TextLinePosition , indexRange: ClosedRange < Int > )
162+ /// Private implementation of ``TextLayoutManager/determineVisiblePosition(for:)``.
163+ ///
164+ /// Separated for readability. This method does not have an optional parameter, and keeps track of a recursion depth.
165+ private func determineVisiblePositionRecursively(
166+ for originalPosition: ( position: TextLineStorage < TextLine > . TextLinePosition , indexRange: ClosedRange < Int > ) ,
167+ recursionDepth: Int
147168 ) -> ( position: TextLineStorage < TextLine > . TextLinePosition , indexRange: ClosedRange < Int > ) ? {
169+ // Arbitrary max recursion depth. Ensures we don't spiral into in an infinite recursion.
170+ guard recursionDepth < 10 else {
171+ logger. warning ( " Visible position recursed for over 10 levels, returning early. " )
172+ return originalPosition
173+ }
174+
148175 let attachments = attachments. get ( overlapping: originalPosition. position. range)
149176 guard let firstAttachment = attachments. first, let lastAttachment = attachments. last else {
150177 // No change, either no attachments or attachment doesn't span multiple lines.
@@ -184,7 +211,10 @@ public extension TextLayoutManager {
184211 return ( newPosition, minIndex... maxIndex)
185212 } else {
186213 // Recurse, to make sure we combine all necessary lines.
187- return determineVisiblePosition ( for: ( newPosition, minIndex... maxIndex) )
214+ return determineVisiblePositionRecursively (
215+ for: ( newPosition, minIndex... maxIndex) ,
216+ recursionDepth: recursionDepth + 1
217+ )
188218 }
189219 }
190220}
0 commit comments