@@ -103,7 +103,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
103
103
104
104
let sourcekitd : SourceKitD
105
105
106
- let clientCapabilities : ClientCapabilities
106
+ let capabilityRegistry : CapabilityRegistry
107
107
108
108
let serverOptions : SourceKitServer . Options
109
109
@@ -122,6 +122,14 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
122
122
var keys : sourcekitd_keys { return sourcekitd. keys }
123
123
var requests : sourcekitd_requests { return sourcekitd. requests }
124
124
var values : sourcekitd_values { return sourcekitd. values }
125
+
126
+ var enablePublishDiagnostics : Bool {
127
+ // Since LSP 3.17.0, diagnostics can be reported through pull-based requests,
128
+ // in addition to the existing push-based publish notifications.
129
+ // If the client supports pull diagnostics, we report the capability
130
+ // and we should disable the publish notifications to avoid double-reporting.
131
+ return capabilityRegistry. pullDiagnosticsRegistration ( for: . swift) == nil
132
+ }
125
133
126
134
private var state : LanguageServerState {
127
135
didSet {
@@ -144,15 +152,14 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
144
152
public init ? (
145
153
client: LocalConnection ,
146
154
toolchain: Toolchain ,
147
- clientCapabilities: ClientCapabilities ? ,
148
155
options: SourceKitServer . Options ,
149
156
workspace: Workspace ,
150
157
reopenDocuments: @escaping ( ToolchainLanguageServer ) -> Void
151
158
) throws {
152
159
guard let sourcekitd = toolchain. sourcekitd else { return nil }
153
160
self . client = client
154
161
self . sourcekitd = try SourceKitDImpl . getOrCreate ( dylibPath: sourcekitd)
155
- self . clientCapabilities = clientCapabilities ?? ClientCapabilities ( workspace: nil , textDocument : nil )
162
+ self . capabilityRegistry = workspace. capabilityRegistry
156
163
self . serverOptions = options
157
164
self . documentManager = DocumentManager ( )
158
165
self . state = . connected
@@ -242,7 +249,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
242
249
243
250
/// Inform the client about changes to the syntax highlighting tokens.
244
251
private func requestTokensRefresh( ) {
245
- if clientCapabilities . workspace ? . semanticTokens ? . refreshSupport ?? false {
252
+ if capabilityRegistry . clientHasSemanticTokenRefreshSupport {
246
253
_ = client. send ( WorkspaceSemanticTokensRefreshRequest ( ) , queue: queue) { result in
247
254
if let error = result. failure {
248
255
log ( " refreshing tokens failed: \( error) " , level: . warning)
@@ -285,8 +292,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
285
292
let stageUID : sourcekitd_uid_t ? = response [ sourcekitd. keys. diagnostic_stage]
286
293
let stage = stageUID. flatMap { DiagnosticStage ( $0, sourcekitd: sourcekitd) } ?? . sema
287
294
288
- let supportsCodeDescription =
289
- ( clientCapabilities. textDocument? . publishDiagnostics? . codeDescriptionSupport == true )
295
+ let supportsCodeDescription = capabilityRegistry. clientHasDiagnosticsCodeDescriptionSupport
290
296
291
297
// Note: we make the notification even if there are no diagnostics to clear the current state.
292
298
var newDiags : [ CachedDiagnostic ] = [ ]
@@ -326,7 +332,10 @@ public final class SwiftLanguageServer: ToolchainLanguageServer {
326
332
req [ keys. sourcetext] = " "
327
333
328
334
if let dict = try ? self . sourcekitd. sendSync ( req) {
329
- publishDiagnostics ( response: dict, for: snapshot, compileCommand: compileCommand)
335
+ if ( enablePublishDiagnostics) {
336
+ publishDiagnostics ( response: dict, for: snapshot, compileCommand: compileCommand)
337
+ }
338
+
330
339
if dict [ keys. diagnostic_stage] as sourcekitd_uid_t ? == sourcekitd. values. diag_stage_sema {
331
340
// Only update semantic tokens if the 0,0 replacetext request returned semantic information.
332
341
updateSemanticTokens ( response: dict, for: snapshot)
@@ -370,7 +379,10 @@ extension SwiftLanguageServer {
370
379
range: . bool( true ) ,
371
380
full: . bool( true ) ) ,
372
381
inlayHintProvider: . value( InlayHintOptions (
373
- resolveProvider: false ) )
382
+ resolveProvider: false ) ) ,
383
+ diagnosticProvider: DiagnosticOptions (
384
+ interFileDependencies: true ,
385
+ workspaceDiagnostics: false )
374
386
) )
375
387
}
376
388
@@ -964,6 +976,7 @@ extension SwiftLanguageServer {
964
976
}
965
977
966
978
public func foldingRange( _ req: Request < FoldingRangeRequest > ) {
979
+ let foldingRangeCapabilities = capabilityRegistry. clientCapabilities. textDocument? . foldingRange
967
980
queue. async {
968
981
guard let snapshot = self . documentManager. latestSnapshot ( req. params. textDocument. uri) else {
969
982
log ( " failed to find snapshot for url \( req. params. textDocument. uri) " )
@@ -1142,17 +1155,16 @@ extension SwiftLanguageServer {
1142
1155
}
1143
1156
}
1144
1157
1145
- let capabilities = self . clientCapabilities. textDocument? . foldingRange
1146
1158
// If the limit is less than one, do nothing.
1147
- if let limit = capabilities ? . rangeLimit, limit <= 0 {
1159
+ if let limit = foldingRangeCapabilities ? . rangeLimit, limit <= 0 {
1148
1160
req. reply ( [ ] )
1149
1161
return
1150
1162
}
1151
1163
1152
1164
let rangeFinder = FoldingRangeFinder (
1153
1165
snapshot: snapshot,
1154
- rangeLimit: capabilities ? . rangeLimit,
1155
- lineFoldingOnly: capabilities ? . lineFoldingOnly ?? false )
1166
+ rangeLimit: foldingRangeCapabilities ? . rangeLimit,
1167
+ lineFoldingOnly: foldingRangeCapabilities ? . lineFoldingOnly ?? false )
1156
1168
rangeFinder. walk ( sourceFile)
1157
1169
let ranges = rangeFinder. finalize ( )
1158
1170
@@ -1167,12 +1179,12 @@ extension SwiftLanguageServer {
1167
1179
]
1168
1180
let wantedActionKinds = req. params. context. only
1169
1181
let providers = providersAndKinds. filter { wantedActionKinds? . contains ( $0. 1 ) != false }
1182
+ let codeActionCapabilities = capabilityRegistry. clientCapabilities. textDocument? . codeAction
1170
1183
retrieveCodeActions ( req, providers: providers. map { $0. provider } ) { result in
1171
1184
switch result {
1172
1185
case . success( let codeActions) :
1173
- let capabilities = self . clientCapabilities. textDocument? . codeAction
1174
1186
let response = CodeActionRequestResponse ( codeActions: codeActions,
1175
- clientCapabilities: capabilities )
1187
+ clientCapabilities: codeActionCapabilities )
1176
1188
req. reply ( response)
1177
1189
case . failure( let error) :
1178
1190
req. reply ( . failure( error) )
@@ -1326,10 +1338,75 @@ extension SwiftLanguageServer {
1326
1338
}
1327
1339
}
1328
1340
}
1341
+
1342
+ // Must be called on self.queue
1343
+ public func _documentDiagnostic(
1344
+ _ uri: DocumentURI ,
1345
+ _ completion: @escaping ( Result < [ Diagnostic ] , ResponseError > ) -> Void
1346
+ ) {
1347
+ dispatchPrecondition ( condition: . onQueue( queue) )
1348
+
1349
+ guard let snapshot = documentManager. latestSnapshot ( uri) else {
1350
+ let msg = " failed to find snapshot for url \( uri) "
1351
+ log ( msg)
1352
+ return completion ( . failure( . unknown( msg) ) )
1353
+ }
1354
+
1355
+ let keys = self . keys
1356
+
1357
+ let skreq = SKDRequestDictionary ( sourcekitd: self . sourcekitd)
1358
+ skreq [ keys. request] = requests. diagnostics
1359
+ skreq [ keys. sourcefile] = snapshot. document. uri. pseudoPath
1360
+
1361
+ // FIXME: SourceKit should probably cache this for us.
1362
+ if let compileCommand = self . commandsByFile [ uri] {
1363
+ skreq [ keys. compilerargs] = compileCommand. compilerArgs
1364
+ }
1365
+
1366
+ let supportsCodeDescription = capabilityRegistry. clientHasDiagnosticsCodeDescriptionSupport
1367
+
1368
+ let handle = self . sourcekitd. send ( skreq, self . queue) { response in
1369
+ guard let dict = response. success else {
1370
+ return completion ( . failure( ResponseError ( response. failure!) ) )
1371
+ }
1372
+
1373
+ var diagnostics : [ Diagnostic ] = [ ]
1374
+ dict [ keys. diagnostics] ? . forEach { _, diag in
1375
+ if let diagnostic = Diagnostic ( diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) {
1376
+ diagnostics. append ( diagnostic)
1377
+ }
1378
+ return true
1379
+ }
1380
+
1381
+ completion ( . success( diagnostics) )
1382
+ }
1383
+
1384
+ // FIXME: cancellation
1385
+ _ = handle
1386
+ }
1329
1387
1388
+ public func documentDiagnostic(
1389
+ _ uri: DocumentURI ,
1390
+ _ completion: @escaping ( Result < [ Diagnostic ] , ResponseError > ) -> Void
1391
+ ) {
1392
+ self . queue. async {
1393
+ self . _documentDiagnostic ( uri, completion)
1394
+ }
1395
+ }
1396
+
1330
1397
public func documentDiagnostic( _ req: Request < DocumentDiagnosticsRequest > ) {
1331
- // TODO: Return provider object in initializeSync and implement pull-model document diagnostics here.
1332
- req. reply ( . failure( . unknown( " Pull-model diagnostics not implemented yet. " ) ) )
1398
+ let uri = req. params. textDocument. uri
1399
+ documentDiagnostic ( req. params. textDocument. uri) { result in
1400
+ switch result {
1401
+ case . success( let diagnostics) :
1402
+ req. reply ( . full( . init( items: diagnostics) ) )
1403
+
1404
+ case . failure( let error) :
1405
+ let message = " document diagnostic failed \( uri) : \( error) "
1406
+ log ( message, level: . warning)
1407
+ return req. reply ( . failure( . unknown( message) ) )
1408
+ }
1409
+ }
1333
1410
}
1334
1411
1335
1412
public func executeCommand( _ req: Request < ExecuteCommandRequest > ) {
0 commit comments