@@ -2,6 +2,7 @@ import AppKit
22import AsyncExtensions
33import AXNotificationStream
44import Foundation
5+ import Logger
56import SuggestionModel
67
78/// Representing a source editor inside Xcode.
@@ -24,43 +25,25 @@ public class SourceEditor {
2425 var observeAXNotificationsTask : Task < Void , Never > ?
2526 public let axNotifications = AsyncPassthroughSubject < AXNotification > ( )
2627
28+ /// To prevent expensive calculations in ``getContent()``.
29+ private let cache = Cache ( )
2730
28- private var cachedContent : String ?
29- private var cachedLines = [ String] ( )
30-
3131 /// Get the content of the source editor.
3232 ///
3333 /// - note: This method is expensive.
3434 public func getContent( ) -> Content {
3535 let content = element. value
36-
37- let split = if element. hashValue == cachedContent? . hashValue {
38- cachedLines
39- } else {
40- content. breakLines ( appendLineBreakToLastLine: false )
41- }
42-
43- cachedContent = content
44- cachedLines = split
45-
36+ let selectionRange = element. selectedTextRange
37+ let ( lines, selections) = cache. get ( content: content, selectedTextRange: selectionRange)
38+
4639 let lineAnnotationElements = element. children. filter { $0. identifier == " Line Annotation " }
4740 let lineAnnotations = lineAnnotationElements. map ( \. description)
4841
49- if let selectionRange = element. selectedTextRange {
50- let range = Self . convertRangeToCursorRange ( selectionRange, in: split)
51- return . init(
52- content: content,
53- lines: split,
54- selections: [ range] ,
55- cursorPosition: range. start,
56- lineAnnotations: lineAnnotations
57- )
58- }
5942 return . init(
6043 content: content,
61- lines: split ,
62- selections: [ ] ,
63- cursorPosition: . outOfScope,
44+ lines: lines ,
45+ selections: selections ,
46+ cursorPosition: selections . first ? . start ?? . outOfScope,
6447 lineAnnotations: lineAnnotations
6548 )
6649 }
@@ -129,6 +112,56 @@ public class SourceEditor {
129112 }
130113}
131114
115+ extension SourceEditor {
116+ final class Cache {
117+ static let queue = DispatchQueue ( label: " SourceEditor.Cache " )
118+
119+ private var sourceContent : String ?
120+ private var cachedLines = [ String] ( )
121+ private var sourceSelectedTextRange : ClosedRange < Int > ?
122+ private var cachedSelections = [ CursorRange] ( )
123+
124+ func get( content: String , selectedTextRange: ClosedRange < Int > ? ) -> (
125+ lines: [ String ] ,
126+ selections: [ CursorRange ]
127+ ) {
128+ Self . queue. sync {
129+ let contentMatch = content. hashValue == sourceContent? . hashValue
130+ let selectedRangeMatch = selectedTextRange == sourceSelectedTextRange
131+ let lines : [ String ] = {
132+ if contentMatch {
133+ Logger . service. debug ( " Cache Hit: Lines " )
134+ return cachedLines
135+ }
136+ Logger . service. debug ( " Cache Missed: Lines " )
137+ return content. breakLines ( appendLineBreakToLastLine: false )
138+ } ( )
139+ let selections : [ CursorRange ] = {
140+ if contentMatch, selectedRangeMatch {
141+ Logger . service. debug ( " Cache Hit: Selections " )
142+ return cachedSelections
143+ }
144+ Logger . service. debug ( " Cache Missed: Selections " )
145+ if let selectedTextRange {
146+ return [ SourceEditor . convertRangeToCursorRange (
147+ selectedTextRange,
148+ in: lines
149+ ) ]
150+ }
151+ return [ ]
152+ } ( )
153+
154+ sourceContent = content
155+ cachedLines = lines
156+ sourceSelectedTextRange = selectedTextRange
157+ cachedSelections = selections
158+
159+ return ( lines, selections)
160+ }
161+ }
162+ }
163+ }
164+
132165// MARK: - Helpers
133166
134167public extension SourceEditor {
0 commit comments