Skip to content

Commit 0e75e95

Browse files
committed
Allow multiple language services to handle a single document
This allows us to implement all of `doccDocumentation` in `DocumentationLanguageService`. `DocumentationLanguageService` will be a secondary language service for Swift files and can also provide the docc documentation support that’s currently in `SwiftLangaugeService`.
1 parent 10c8991 commit 0e75e95

13 files changed

+295
-203
lines changed

Sources/DocumentationLanguageService/DoccDocumentationHandler.swift

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,32 +63,29 @@ extension DocumentationLanguageService {
6363
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
6464
ofDocCSymbolLink: symbolLink,
6565
fetchSymbolGraph: { location in
66-
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri),
67-
let languageService = await sourceKitLSPServer.languageService(
68-
for: location.documentUri,
69-
.swift,
70-
in: symbolWorkspace
71-
)
72-
else {
66+
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
7367
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
7468
}
69+
let languageService = try await sourceKitLSPServer.primaryLanguageService(
70+
for: location.documentUri,
71+
.swift,
72+
in: symbolWorkspace
73+
)
7574
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)
7675
}
7776
)
7877
else {
7978
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
8079
}
8180
let symbolDocumentUri = symbolOccurrence.location.documentUri
82-
guard
83-
let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri),
84-
let languageService = await sourceKitLSPServer.languageService(
85-
for: symbolDocumentUri,
86-
.swift,
87-
in: symbolWorkspace
88-
)
89-
else {
81+
guard let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri) else {
9082
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
9183
}
84+
let languageService = try await sourceKitLSPServer.primaryLanguageService(
85+
for: symbolDocumentUri,
86+
.swift,
87+
in: symbolWorkspace
88+
)
9289
let symbolGraph = try await languageService.symbolGraph(
9390
forOnDiskContentsOf: symbolDocumentUri,
9491
at: symbolOccurrence.location

Sources/DocumentationLanguageService/DocumentationLanguageService.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,6 @@ package actor DocumentationLanguageService: LanguageService, Sendable {
5151
return await sourceKitLSPServer.workspaceForDocument(uri: uri)
5252
}
5353

54-
func languageService(
55-
for uri: DocumentURI,
56-
_ language: LanguageServerProtocol.Language,
57-
in workspace: Workspace
58-
) async throws -> LanguageService? {
59-
guard let sourceKitLSPServer else {
60-
throw ResponseError.unknown("Connection to the editor closed")
61-
}
62-
return await sourceKitLSPServer.languageService(for: uri, language, in: workspace)
63-
}
64-
6554
package nonisolated func canHandle(workspace: Workspace, toolchain: Toolchain) -> Bool {
6655
return true
6756
}

Sources/LanguageServerProtocol/Error.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public struct ErrorCode: RawRepresentable, Codable, Hashable, Sendable {
8585
public static let workspaceNotOpen: ErrorCode = ErrorCode(rawValue: -32003)
8686

8787
/// The method is not implemented in this `LanguageService`.
88+
///
89+
/// This informs `SourceKitLSPServer` that it should query secondary language services for the results.
8890
public static let requestNotImplemented: ErrorCode = ErrorCode(rawValue: -32004)
8991
}
9092

Sources/SourceKitLSP/LanguageServiceRegistry.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,27 +34,35 @@ struct LanguageServiceType: Hashable {
3434
/// Registry in which conformers to `LanguageService` can be registered to server semantic functionality for a set of
3535
/// languages.
3636
package struct LanguageServiceRegistry {
37-
private var byLanguage: [Language: LanguageService.Type] = [:]
37+
private var byLanguage: [Language: [LanguageServiceType]] = [:]
3838

3939
package init() {}
4040

4141
package mutating func register(_ languageService: LanguageService.Type, for languages: [Language]) {
4242
for language in languages {
43-
if let existingLanguageService = byLanguage[language] {
44-
logger.fault(
45-
"Cannot register \(languageService) for \(language, privacy: .public) because \(existingLanguageService) is already registered"
46-
)
43+
let services = byLanguage[language] ?? []
44+
if services.contains(LanguageServiceType(languageService)) {
45+
logger.fault("\(languageService) already registered for \(language, privacy: .public)")
4746
continue
4847
}
49-
byLanguage[language] = languageService
48+
byLanguage[language, default: []].append(LanguageServiceType(languageService))
5049
}
5150
}
5251

53-
func languageService(for language: Language) -> LanguageService.Type? {
54-
return byLanguage[language]
52+
/// The language services that can handle a document of the given language.
53+
///
54+
/// Multiple language services may be able to handle a document. Depending on the use case, callers need to combine
55+
/// the results of the language services.
56+
/// If it is possible to merge the results of the language service (eg. combining code actions from multiple language
57+
/// services), that's the preferred choice.
58+
/// Otherwise the language services occurring early in the array should be given precedence and the results of the
59+
/// first language service that produces some should be returned.
60+
func languageServices(for language: Language) -> [LanguageService.Type] {
61+
return byLanguage[language]?.map(\.type) ?? []
5562
}
5663

64+
/// All language services that are registered in the registry.
5765
var languageServices: Set<LanguageServiceType> {
58-
return Set(byLanguage.values.map { LanguageServiceType($0) })
66+
return Set(byLanguage.values.flatMap { $0 })
5967
}
6068
}

Sources/SourceKitLSP/Rename.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ extension SourceKitLSPServer {
124124
guard let snapshot = self.documentManager.latestSnapshotOrDisk(uri, language: .swift) else {
125125
return nil
126126
}
127-
let swiftLanguageService = await self.languageService(for: uri, .swift, in: workspace) as? NameTranslatorService
127+
let swiftLanguageService = await orLog("Getting NameTranslatorService") {
128+
try await self.primaryLanguageService(for: uri, .swift, in: workspace) as? NameTranslatorService
129+
}
128130
guard let swiftLanguageService else {
129131
return nil
130132
}
@@ -210,7 +212,7 @@ extension SourceKitLSPServer {
210212
return CrossLanguageName(clangName: definitionName, swiftName: swiftName, definitionLanguage: definitionLanguage)
211213
case .swift:
212214
guard
213-
let swiftLanguageService = await self.languageService(
215+
let swiftLanguageService = try await self.primaryLanguageService(
214216
for: definitionDocumentUri,
215217
definitionLanguage,
216218
in: workspace
@@ -278,9 +280,7 @@ extension SourceKitLSPServer {
278280
guard let workspace = await workspaceForDocument(uri: uri) else {
279281
throw ResponseError.workspaceNotOpen(uri)
280282
}
281-
guard let primaryFileLanguageService = workspace.documentService(for: uri) else {
282-
return nil
283-
}
283+
let primaryFileLanguageService = try await primaryLanguageService(for: uri, snapshot.language, in: workspace)
284284

285285
// Determine the local edits and the USR to rename
286286
let renameResult = try await primaryFileLanguageService.rename(request)
@@ -395,7 +395,10 @@ extension SourceKitLSPServer {
395395
logger.error("Failed to get document snapshot for \(uri.forLogging)")
396396
return nil
397397
}
398-
guard let languageService = await self.languageService(for: uri, language, in: workspace) else {
398+
let languageService = await orLog("Getting language service to compute edits in file") {
399+
try await self.primaryLanguageService(for: uri, language, in: workspace)
400+
}
401+
guard let languageService else {
399402
return nil
400403
}
401404

0 commit comments

Comments
 (0)