@@ -27,13 +27,15 @@ final class SemanticTokenHighlightProvider<Storage: SemanticTokenStorage>: Highl
27
27
}
28
28
29
29
typealias EditCallback = @MainActor ( Result < IndexSet , any Error > ) -> Void
30
+ typealias HighlightCallback = @MainActor ( Result < [ HighlightRange ] , any Error > ) -> Void
30
31
31
32
private let tokenMap : SemanticTokenMap
32
33
private let documentURI : String
33
34
private weak var languageServer : LanguageServer ?
34
35
private weak var textView : TextView ?
35
36
36
37
private var lastEditCallback : EditCallback ?
38
+ private var pendingHighlightCallbacks : [ HighlightCallback ] = [ ]
37
39
private var storage : Storage
38
40
39
41
var documentRange : NSRange {
@@ -47,34 +49,92 @@ final class SemanticTokenHighlightProvider<Storage: SemanticTokenStorage>: Highl
47
49
self . storage = Storage ( )
48
50
}
49
51
52
+ // MARK: - Language Server Content Lifecycle
53
+
54
+ /// Called when the language server finishes sending a document update.
55
+ ///
56
+ /// This method first checks if this object has any semantic tokens. If not, requests new tokens and responds to the
57
+ /// `pendingHighlightCallbacks` queue with cancellation errors, causing the highlighter to re-query those indices.
58
+ ///
59
+ /// If this object already has some tokens, it determines whether or not we can request a token delta and
60
+ /// performs the request.
50
61
func documentDidChange( ) async throws {
51
62
guard let languageServer, let textView else {
52
63
return
53
64
}
54
- print ( " Doc did change " )
55
-
56
- // The document was updated. Update our cache and send the invalidated ranges for the editor to handle.
57
- if let lastRequestId = storage. lastRequestId {
58
- guard let response = try await languageServer. requestSemanticTokens ( // Not sure why these are optional...
59
- for: documentURI,
60
- previousResultId: lastRequestId
61
- ) else {
62
- return
63
- }
64
- switch response {
65
- case let . optionA( tokenData) :
66
- await applyEntireResponse ( tokenData, callback: lastEditCallback)
67
- case let . optionB( deltaData) :
68
- await applyDeltaResponse ( deltaData, callback: lastEditCallback, textView: textView)
69
- }
70
- } else {
71
- guard let response = try await languageServer. requestSemanticTokens ( for: documentURI) else {
72
- return
65
+
66
+ guard storage. hasTokens else {
67
+ // We have no semantic token info, request it!
68
+ try await requestTokens ( languageServer: languageServer, textView: textView)
69
+ await MainActor . run {
70
+ for callback in pendingHighlightCallbacks {
71
+ callback ( . failure( HighlightProvidingError . operationCancelled) )
72
+ }
73
+ pendingHighlightCallbacks. removeAll ( )
73
74
}
74
- await applyEntireResponse ( response, callback: lastEditCallback)
75
+ return
76
+ }
77
+
78
+ // The document was updated. Update our token cache and send the invalidated ranges for the editor to handle.
79
+ if let lastResultId = storage. lastResultId {
80
+ try await requestDeltaTokens ( languageServer: languageServer, textView: textView, lastResultId: lastResultId)
81
+ return
82
+ }
83
+
84
+ try await requestTokens ( languageServer: languageServer, textView: textView)
85
+ }
86
+
87
+ // MARK: - LSP Token Requests
88
+
89
+ /// Requests and applies a token delta. Requires a previous response identifier.
90
+ private func requestDeltaTokens(
91
+ languageServer: LanguageServer ,
92
+ textView: TextView ,
93
+ lastResultId: String
94
+ ) async throws {
95
+ guard let response = try await languageServer. requestSemanticTokens (
96
+ for: documentURI,
97
+ previousResultId: lastResultId
98
+ ) else {
99
+ return
100
+ }
101
+ switch response {
102
+ case let . optionA( tokenData) :
103
+ await applyEntireResponse ( tokenData, callback: lastEditCallback)
104
+ case let . optionB( deltaData) :
105
+ await applyDeltaResponse ( deltaData, callback: lastEditCallback, textView: textView)
106
+ }
107
+ }
108
+
109
+ /// Requests and applies tokens for an entire document. This does not require a previous response id, and should be
110
+ /// used in place of `requestDeltaTokens` when that's the case.
111
+ private func requestTokens( languageServer: LanguageServer , textView: TextView ) async throws {
112
+ guard let response = try await languageServer. requestSemanticTokens ( for: documentURI) else {
113
+ return
114
+ }
115
+ await applyEntireResponse ( response, callback: lastEditCallback)
116
+ }
117
+
118
+ // MARK: - Apply LSP Response
119
+
120
+ /// Applies a delta response from the LSP to our storage.
121
+ private func applyDeltaResponse( _ data: SemanticTokensDelta , callback: EditCallback ? , textView: TextView ? ) async {
122
+ let lspRanges = storage. applyDelta ( data)
123
+ lastEditCallback = nil // Don't use this callback again.
124
+ await MainActor . run {
125
+ let ranges = lspRanges. compactMap { textView? . nsRangeFrom ( $0) }
126
+ callback ? ( . success( IndexSet ( ranges: ranges) ) )
75
127
}
76
128
}
77
129
130
+ private func applyEntireResponse( _ data: SemanticTokens , callback: EditCallback ? ) async {
131
+ storage. setData ( data)
132
+ lastEditCallback = nil // Don't use this callback again.
133
+ await callback ? ( . success( IndexSet ( integersIn: documentRange) ) )
134
+ }
135
+
136
+ // MARK: - Highlight Provider Conformance
137
+
78
138
func setUp( textView: TextView , codeLanguage: CodeLanguage ) {
79
139
// Send off a request to get the initial token data
80
140
self . textView = textView
@@ -90,12 +150,12 @@ final class SemanticTokenHighlightProvider<Storage: SemanticTokenStorage>: Highl
90
150
lastEditCallback = completion
91
151
}
92
152
93
- func queryHighlightsFor(
94
- textView : TextView ,
95
- range : NSRange ,
96
- completion : @escaping @ MainActor ( Result < [ HighlightRange ] , any Error > ) -> Void
97
- ) {
98
- print ( " Querying highlights " )
153
+ func queryHighlightsFor( textView : TextView , range : NSRange , completion : @escaping HighlightCallback ) {
154
+ guard storage . hasTokens else {
155
+ pendingHighlightCallbacks . append ( completion )
156
+ return
157
+ }
158
+
99
159
guard let lspRange = textView. lspRangeFrom ( nsRange: range) else {
100
160
completion ( . failure( HighlightError . lspRangeFailure) )
101
161
return
@@ -104,24 +164,4 @@ final class SemanticTokenHighlightProvider<Storage: SemanticTokenStorage>: Highl
104
164
let highlights = tokenMap. decode ( tokens: rawTokens, using: textView)
105
165
completion ( . success( highlights) )
106
166
}
107
-
108
- // MARK: - Apply Response
109
-
110
- private func applyDeltaResponse( _ data: SemanticTokensDelta , callback: EditCallback ? , textView: TextView ? ) async {
111
- print ( " Applying delta: \( data) " )
112
- let lspRanges = storage. applyDelta ( data, requestId: data. resultId)
113
- await MainActor . run {
114
- let ranges = lspRanges. compactMap { textView? . nsRangeFrom ( $0) }
115
- callback ? ( . success( IndexSet ( ranges: ranges) ) )
116
- }
117
- lastEditCallback = nil // Don't use this callback again.
118
- }
119
-
120
- private func applyEntireResponse( _ data: SemanticTokens , callback: EditCallback ? ) async {
121
- print ( " Applying entire: \( data) " )
122
- storage. setData ( data)
123
- await callback ? ( . success( IndexSet ( integersIn: documentRange) ) )
124
- lastEditCallback = nil // Don't use this callback again.
125
- }
126
-
127
167
}
0 commit comments