@@ -161,77 +161,100 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
161
161
}
162
162
}
163
163
164
- /// Update lexical and syntactic tokens for the given `snapshot`.
165
- /// Should only be called on `queue`.
164
+ /// Updates the lexical and syntactic tokens for the given `snapshot`.
165
+ /// Must be called on `self. queue`.
166
166
private func updateLexicalAndSyntacticTokens(
167
167
response: SKDResponseDictionary ,
168
168
for snapshot: DocumentSnapshot
169
169
) {
170
170
dispatchPrecondition ( condition: . onQueue( queue) )
171
171
172
+ let uri = snapshot. document. uri
173
+ let docTokens = updatedLexicalAndSyntacticTokens ( response: response, for: snapshot)
174
+
175
+ do {
176
+ try documentManager. updateTokens ( uri, tokens: docTokens)
177
+ } catch {
178
+ log ( " Updating lexical and syntactic tokens failed: \( error) " , level: . warning)
179
+ }
180
+ }
181
+
182
+ /// Returns the updated lexical and syntactic tokens for the given `snapshot`.
183
+ private func updatedLexicalAndSyntacticTokens(
184
+ response: SKDResponseDictionary ,
185
+ for snapshot: DocumentSnapshot
186
+ ) -> DocumentTokens {
187
+ var docTokens = snapshot. tokens
188
+
172
189
guard let offset: Int = response [ keys. offset] ,
173
190
let length: Int = response [ keys. length] ,
174
191
let start: Position = snapshot. positionOf ( utf8Offset: offset) ,
175
192
let end: Position = snapshot. positionOf ( utf8Offset: offset + length) else {
176
193
log ( " updateLexicalAndSyntacticTokens failed, no range found " , level: . error)
177
- return
194
+ return docTokens
178
195
}
179
196
180
- let uri = snapshot. document. uri
181
197
let range = start..< end
182
198
183
199
// If the range is empty we don't have to (and shouldn't) update anything.
184
200
// This is important, since the substructure may be empty, causing us to
185
201
// unnecessarily remove all syntactic tokens.
186
202
guard !range. isEmpty else {
187
- return
203
+ return docTokens
188
204
}
189
205
190
206
if let syntaxMap: SKDResponseArray = response [ keys. syntaxmap] {
191
207
let tokenParser = SyntaxHighlightingTokenParser ( sourcekitd: sourcekitd)
192
208
let tokens = tokenParser. parseTokens ( syntaxMap, in: snapshot)
193
209
194
- do {
195
- try documentManager. replaceLexicalTokens ( uri, in: range, with: tokens)
196
- } catch {
197
- log ( " updating lexical tokens for \( uri) failed: \( error) " , level: . warning)
198
- }
210
+ docTokens. replaceLexical ( in: range, with: tokens)
199
211
}
200
212
201
213
if let substructure: SKDResponseArray = response [ keys. substructure] {
202
214
let tokenParser = SyntaxHighlightingTokenParser ( sourcekitd: sourcekitd, useName: true )
203
215
let tokens = tokenParser. parseTokens ( substructure, in: snapshot)
204
- do {
205
- try documentManager. replaceSyntacticTokens ( uri, tokens: tokens)
206
- } catch {
207
- log ( " updating syntactic tokens for \( uri) failed: \( error) " , level: . warning)
208
- }
216
+
217
+ docTokens. syntactic = tokens
209
218
}
219
+
220
+ return docTokens
210
221
}
211
222
212
- /// Update semantic tokens for the given `snapshot`.
213
- /// Should only be called on `queue`.
223
+ /// Updates the semantic tokens for the given `snapshot`.
224
+ /// Must be called on `self. queue`.
214
225
private func updateSemanticTokens(
215
226
response: SKDResponseDictionary ,
216
227
for snapshot: DocumentSnapshot
217
228
) {
218
229
dispatchPrecondition ( condition: . onQueue( queue) )
219
230
220
231
let uri = snapshot. document. uri
221
- guard let skTokens: SKDResponseArray = response [ keys. annotations] else {
222
- return
223
- }
224
-
225
- let tokenParser = SyntaxHighlightingTokenParser ( sourcekitd: sourcekitd)
226
- let tokens = tokenParser. parseTokens ( skTokens, in: snapshot)
232
+ let docTokens = updatedSemanticTokens ( response: response, for: snapshot)
227
233
228
234
do {
229
- try documentManager. replaceSemanticTokens ( uri, tokens: tokens )
235
+ try documentManager. updateTokens ( uri, tokens: docTokens )
230
236
} catch {
231
- log ( " updating semantic tokens for \( uri ) failed: \( error) " , level: . warning)
237
+ log ( " Updating semantic tokens failed: \( error) " , level: . warning)
232
238
}
233
239
}
234
240
241
+ /// Returns the updated semantic tokens for the given `snapshot`.
242
+ private func updatedSemanticTokens(
243
+ response: SKDResponseDictionary ,
244
+ for snapshot: DocumentSnapshot
245
+ ) -> DocumentTokens {
246
+ var docTokens = snapshot. tokens
247
+
248
+ if let skTokens: SKDResponseArray = response [ keys. annotations] {
249
+ let tokenParser = SyntaxHighlightingTokenParser ( sourcekitd: sourcekitd)
250
+ let tokens = tokenParser. parseTokens ( skTokens, in: snapshot)
251
+
252
+ docTokens. semantic = tokens
253
+ }
254
+
255
+ return docTokens
256
+ }
257
+
235
258
/// Inform the client about changes to the syntax highlighting tokens.
236
259
private func requestTokensRefresh( ) {
237
260
if clientCapabilities. workspace? . semanticTokens? . refreshSupport ?? false {
@@ -491,50 +514,35 @@ extension SwiftLanguageServer {
491
514
492
515
self . queue. async {
493
516
let uri = note. textDocument. uri
494
- let newVersion = note. textDocument. version ?? - 1
495
517
var lastResponse : SKDResponseDictionary ? = nil
496
518
497
- // We iterate manually over the `contentChanges` here instead of passing
498
- // the notification directly to the convenience method `edit(_:editCallback:)`
499
- // since we need to update the tokens in lockstep with the edits in the
500
- // document.
501
-
502
- do {
503
- for edit in note. contentChanges {
504
- try self . documentManager. edit ( uri, newVersion: newVersion, edits: [ edit] ) { ( before: DocumentSnapshot , edit: TextDocumentContentChangeEvent ) in
505
- let req = SKDRequestDictionary ( sourcekitd: self . sourcekitd)
506
- req [ keys. request] = self . requests. editor_replacetext
507
- req [ keys. name] = note. textDocument. uri. pseudoPath
508
-
509
- if let range = edit. range {
510
- guard let offset = before. utf8Offset ( of: range. lowerBound) , let end = before. utf8Offset ( of: range. upperBound) else {
511
- fatalError ( " invalid edit \( range) " )
512
- }
519
+ self . documentManager. edit ( note) { ( before: DocumentSnapshot , edit: TextDocumentContentChangeEvent ) in
520
+ let req = SKDRequestDictionary ( sourcekitd: self . sourcekitd)
521
+ req [ keys. request] = self . requests. editor_replacetext
522
+ req [ keys. name] = note. textDocument. uri. pseudoPath
513
523
514
- req [ keys. offset] = offset
515
- req [ keys. length] = end - offset
516
-
517
- } else {
518
- // Full text
519
- req [ keys. offset] = 0
520
- req [ keys. length] = before. text. utf8. count
521
- }
522
-
523
- req [ keys. sourcetext] = edit. text
524
-
525
- lastResponse = try ? self . sourcekitd. sendSync ( req)
524
+ if let range = edit. range {
525
+ guard let offset = before. utf8Offset ( of: range. lowerBound) , let end = before. utf8Offset ( of: range. upperBound) else {
526
+ fatalError ( " invalid edit \( range) " )
526
527
}
527
528
528
- // SourceKit seems to respond with an empty substructure after sending an
529
- // empty range for an edit, causing all syntactic tokens to get removed
530
- // therefore we only update them if the range is non-empty.
529
+ req [ keys. offset] = offset
530
+ req [ keys. length] = end - offset
531
531
532
- if let dict = lastResponse, let snapshot = self . documentManager. latestSnapshot ( uri) {
533
- self . updateLexicalAndSyntacticTokens ( response: dict, for: snapshot)
534
- }
532
+ } else {
533
+ // Full text
534
+ req [ keys. offset] = 0
535
+ req [ keys. length] = before. text. utf8. count
536
+ }
537
+
538
+ req [ keys. sourcetext] = edit. text
539
+ lastResponse = try ? self . sourcekitd. sendSync ( req)
540
+ } afterCallback: { ( after: DocumentSnapshot ) in
541
+ if let dict = lastResponse {
542
+ return self . updatedLexicalAndSyntacticTokens ( response: dict, for: after)
543
+ } else {
544
+ return nil
535
545
}
536
- } catch {
537
- log ( " failed to edit document: \( error) " , level: . error)
538
546
}
539
547
540
548
if let dict = lastResponse, let snapshot = self . documentManager. latestSnapshot ( uri) {
0 commit comments