@@ -24,6 +24,9 @@ final class EditorTextView: NSTextView {
2424 /// Callback when user clicks at a different position (to dismiss completion)
2525 var onClickOutsideCompletion : ( ( ) -> Void ) ?
2626
27+ /// Track the last cursor position for smart invalidation
28+ private var lastCursorLine : Int = - 1
29+
2730 // MARK: - Auto-Pairing Configuration
2831
2932 private let bracketPairs : [ Character : Character ] = [
@@ -56,9 +59,6 @@ final class EditorTextView: NSTextView {
5659 commonInit ( )
5760 }
5861
59- /// Track the last cursor position for smart invalidation
60- private var lastCursorLine : Int = - 1
61-
6262 private func commonInit( ) {
6363 // Observe selection changes for visual updates
6464 NotificationCenter . default. addObserver (
@@ -150,22 +150,41 @@ final class EditorTextView: NSTextView {
150150 lastCursorLine = currentLine
151151 }
152152
153+ /// Simple cache for line lookups to avoid repeated O(n) scans for consecutive lines
154+ private var lineCache : ( lastLine: Int , charIndex: Int , searchRange: NSRange ) ?
155+
153156 /// Get the rect for a specific line number using efficient NSString lineRange
154157 private func lineRectForLine( _ lineNumber: Int , layoutManager: NSLayoutManager , textContainer: NSTextContainer ) -> NSRect ? {
155158 guard layoutManager. numberOfGlyphs > 0 else { return nil }
156159
157160 let text = string as NSString
158161 guard text. length > 0 else { return nil }
159162
160- // Find the character index for the target line using NSString's lineRange
161163 var charIndex = 0
162164 var searchRange = NSRange ( location: 0 , length: text. length)
165+ var startLine = 0
166+
167+ // Use cache if we're looking for a nearby line (common case: prev/current line)
168+ if let cache = lineCache, abs ( cache. lastLine - lineNumber) <= 1 {
169+ if cache. lastLine == lineNumber {
170+ // Exact cache hit
171+ charIndex = cache. charIndex
172+ searchRange = cache. searchRange
173+ startLine = lineNumber
174+ } else if cache. lastLine + 1 == lineNumber {
175+ // Next line after cached (common: moving from prev to current)
176+ charIndex = cache. charIndex
177+ searchRange = cache. searchRange
178+ startLine = cache. lastLine
179+ }
180+ }
163181
164- // Iterate to the target line
165- for _ in 0 ..< lineNumber {
182+ // Iterate from cached position (or start) to the target line
183+ for _ in startLine ..< lineNumber {
166184 guard searchRange. location < text. length else {
167185 // Line number is beyond document, clamp to last valid position
168186 charIndex = max ( 0 , text. length - 1 )
187+ lineCache = nil // Invalidate cache for out-of-bounds
169188 break
170189 }
171190
@@ -174,11 +193,12 @@ final class EditorTextView: NSTextView {
174193 // Move to next line
175194 searchRange. location = NSMaxRange ( lineRange)
176195 searchRange. length = text. length - searchRange. location
177-
178- // Set charIndex to the start of the next line
179196 charIndex = searchRange. location
180197 }
181198
199+ // Cache the result for next lookup
200+ lineCache = ( lineNumber, charIndex, searchRange)
201+
182202 // If we reached the target line, charIndex is already set to its start
183203 // Otherwise it was clamped to the last valid position
184204
0 commit comments