@@ -64,12 +64,6 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
64
64
65
65
let clangdOptions : [ String ]
66
66
67
- /// Resolved build settings by file. Must be accessed with the `lock`.
68
- private var buildSettingsByFile : [ DocumentURI : ClangBuildSettings ] = [ : ]
69
-
70
- /// Lock protecting `buildSettingsByFile`.
71
- private var lock : NSLock = NSLock ( )
72
-
73
67
/// The current state of the `clangd` language server.
74
68
/// Changing the property automatically notified the state change handlers.
75
69
private var state : LanguageServerState {
@@ -102,6 +96,10 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
102
96
/// A callback with which `ClangLanguageServer` can request its owner to reopen all documents in case it has crashed.
103
97
private let reopenDocuments : ( ToolchainLanguageServer ) -> Void
104
98
99
+ /// The documents that have been opened and which language they have been
100
+ /// opened with.
101
+ private var openDocuments : [ DocumentURI : Language ] = [ : ]
102
+
105
103
/// While `clangd` is running, its PID.
106
104
#if os(Windows)
107
105
private var hClangd : HANDLE = INVALID_HANDLE_VALUE
@@ -132,6 +130,16 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
132
130
try startClangdProcesss ( )
133
131
}
134
132
133
+ private func buildSettings( for document: DocumentURI ) async -> ClangBuildSettings ? {
134
+ guard let workspace = workspace. value, let language = openDocuments [ document] else {
135
+ return nil
136
+ }
137
+ guard let settings = await workspace. buildSystemManager. buildSettings ( for: document, language: language) else {
138
+ return nil
139
+ }
140
+ return ClangBuildSettings ( settings. buildSettings, clangPath: clangdPath, isFallback: settings. isFallback)
141
+ }
142
+
135
143
nonisolated func canHandle( workspace: Workspace ) -> Bool {
136
144
// We launch different clangd instance for each workspace because clangd doesn't have multi-root workspace support.
137
145
return workspace === self . workspace. value
@@ -160,8 +168,8 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
160
168
161
169
/// Start the `clangd` process, either on creation of the `ClangLanguageServerShim` or after `clangd` has crashed.
162
170
private func startClangdProcesss( ) throws {
163
- // Since we are starting a new clangd process, reset the build settings we have transmitted to clangd
164
- buildSettingsByFile = [ : ]
171
+ // Since we are starting a new clangd process, reset the list of open document
172
+ openDocuments = [ : ]
165
173
166
174
let usToClangd : Pipe = Pipe ( )
167
175
let clangdToUs : Pipe = Pipe ( )
@@ -249,9 +257,9 @@ actor ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler {
249
257
/// sending a notification that's intended for the editor.
250
258
///
251
259
/// We should either handle it ourselves or forward it to the client.
252
- func handle( _ params: some NotificationType , from clientID: ObjectIdentifier ) {
260
+ func handle( _ params: some NotificationType , from clientID: ObjectIdentifier ) async {
253
261
if let publishDiags = params as? PublishDiagnosticsNotification {
254
- self . publishDiagnostics ( Notification ( publishDiags, clientID: clientID) )
262
+ await self . publishDiagnostics ( Notification ( publishDiags, clientID: clientID) )
255
263
} else if clientID == ObjectIdentifier ( self . clangd) {
256
264
self . client. send ( params)
257
265
}
@@ -332,19 +340,27 @@ extension ClangLanguageServerShim {
332
340
333
341
/// Intercept clangd's `PublishDiagnosticsNotification` to withold it if we're using fallback
334
342
/// build settings.
335
- func publishDiagnostics( _ note: Notification < PublishDiagnosticsNotification > ) {
343
+ func publishDiagnostics( _ note: Notification < PublishDiagnosticsNotification > ) async {
336
344
let params = note. params
337
- let buildSettings = self . lock. withLock {
338
- return self . buildSettingsByFile [ params. uri]
339
- }
340
- let isFallback = buildSettings? . isFallback ?? true
341
- guard isFallback else {
345
+ // Technically, the publish diagnostics notification could still originate
346
+ // from when we opened the file with fallback build settings and we could
347
+ // have received real build settings since, which haven't been acknowledged
348
+ // by clangd yet.
349
+ //
350
+ // Since there is no way to tell which build settings clangd used to generate
351
+ // the diagnostics, there's no good way to resolve this race. For now, this
352
+ // should be good enough since the time in which the race may occur is pretty
353
+ // short and we expect clangd to send us new diagnostics with the updated
354
+ // non-fallback settings very shortly after, which will override the
355
+ // incorrect result, making it very temporary.
356
+ let buildSettings = await self . buildSettings ( for: params. uri)
357
+ if buildSettings? . isFallback ?? true {
358
+ // Fallback: send empty publish notification instead.
359
+ client. send ( PublishDiagnosticsNotification (
360
+ uri: params. uri, version: params. version, diagnostics: [ ] ) )
361
+ } else {
342
362
client. send ( note. params)
343
- return
344
363
}
345
- // Fallback: send empty publish notification instead.
346
- client. send ( PublishDiagnosticsNotification (
347
- uri: params. uri, version: params. version, diagnostics: [ ] ) )
348
364
}
349
365
350
366
}
@@ -383,16 +399,18 @@ extension ClangLanguageServerShim {
383
399
384
400
// MARK: - Text synchronization
385
401
386
- public func openDocument( _ note: DidOpenTextDocumentNotification ) {
402
+ public func openDocument( _ note: DidOpenTextDocumentNotification ) async {
403
+ openDocuments [ note. textDocument. uri] = note. textDocument. language
404
+ // Send clangd the build settings for the new file. We need to do this before
405
+ // sending the open notification, so that the initial diagnostics already
406
+ // have build settings.
407
+ await documentUpdatedBuildSettings ( note. textDocument. uri, change: . removedOrUnavailable)
387
408
clangd. send ( note)
388
409
}
389
410
390
411
public func closeDocument( _ note: DidCloseTextDocumentNotification ) {
412
+ openDocuments [ note. textDocument. uri] = nil
391
413
clangd. send ( note)
392
-
393
- // Don't clear cached build settings since we've already informed clangd of the settings for the
394
- // file; if we clear the build settings here we should give clangd dummy build settings to make
395
- // sure we're in sync.
396
414
}
397
415
398
416
public func changeDocument( _ note: DidChangeTextDocumentNotification ) {
@@ -409,26 +427,18 @@ extension ClangLanguageServerShim {
409
427
410
428
// MARK: - Build System Integration
411
429
412
- public func documentUpdatedBuildSettings( _ uri: DocumentURI , change: FileBuildSettingsChange ) {
430
+ public func documentUpdatedBuildSettings( _ uri: DocumentURI , change: FileBuildSettingsChange ) async {
413
431
guard let url = uri. fileURL else {
414
432
// FIXME: The clang workspace can probably be reworked to support non-file URIs.
415
433
log ( " Received updated build settings for non-file URI ' \( uri) '. Ignoring the update. " )
416
434
return
417
435
}
418
- let clangBuildSettings = ClangBuildSettings ( change : change , clangPath : self . clangPath )
436
+ let clangBuildSettings = await self . buildSettings ( for : uri )
419
437
logAsync ( level: clangBuildSettings == nil ? . warning : . debug) { _ in
420
438
let settingsStr = clangBuildSettings == nil ? " nil " : clangBuildSettings!. compilerArgs. description
421
439
return " settings for \( uri) : \( settingsStr) "
422
440
}
423
441
424
- let changed = lock. withLock { ( ) -> Bool in
425
- let prevBuildSettings = self . buildSettingsByFile [ uri]
426
- guard clangBuildSettings != prevBuildSettings else { return false }
427
- self . buildSettingsByFile [ uri] = clangBuildSettings
428
- return true
429
- }
430
- guard changed else { return }
431
-
432
442
// The compile command changed, send over the new one.
433
443
// FIXME: what should we do if we no longer have valid build settings?
434
444
if
0 commit comments