Skip to content

Commit 22fae21

Browse files
committed
Handle documentation requests for Swift files in DocumentationLanguageService
This cleanly separates the responsibilities for handling documentation from those of handling Swift files. It also simplifies providing docc support for clang because we just need to implement the two `symbolGraph` methods in `ClangLanguageService` and can re-use the remaining infrastructure from `DoccLanguageService`.
1 parent 45d5bfb commit 22fae21

File tree

7 files changed

+338
-267
lines changed

7 files changed

+338
-267
lines changed

Sources/ClangLanguageService/ClangLanguageService.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,15 @@ extension ClangLanguageService {
511511
package func symbolGraph(
512512
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
513513
at location: SymbolLocation
514-
) async throws -> String? {
515-
return nil
514+
) async throws -> String {
515+
throw ResponseError.internalError("Symbol graph is currently not supported for clang files")
516+
}
517+
518+
package func symbolGraph(
519+
for snapshot: SourceKitLSP.DocumentSnapshot,
520+
at position: LanguageServerProtocol.Position
521+
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String]) {
522+
throw ResponseError.internalError("Symbol graph is currently not supported for clang files")
516523
}
517524

518525
package func documentSymbolHighlight(_ req: DocumentHighlightRequest) async throws -> [DocumentHighlight]? {

Sources/DocumentationLanguageService/DoccDocumentationHandler.swift

Lines changed: 178 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Markdown
2020
import SKUtilities
2121
import SourceKitLSP
2222
import SemanticIndex
23+
import SKLogging
2324

2425
extension DocumentationLanguageService {
2526
package func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
@@ -29,7 +30,6 @@ extension DocumentationLanguageService {
2930
guard let workspace = await sourceKitLSPServer.workspaceForDocument(uri: req.textDocument.uri) else {
3031
throw ResponseError.workspaceNotOpen(req.textDocument.uri)
3132
}
32-
let documentationManager = workspace.doccDocumentationManager
3333
let snapshot = try documentManager.latestSnapshot(req.textDocument.uri)
3434
var moduleName: String? = nil
3535
var catalogURL: URL? = nil
@@ -40,81 +40,197 @@ extension DocumentationLanguageService {
4040

4141
switch snapshot.language {
4242
case .tutorial:
43-
return try await documentationManager.renderDocCDocumentation(
44-
tutorialFile: snapshot.text,
43+
return try await tutorialDocumentation(
44+
for: snapshot,
45+
in: workspace,
4546
moduleName: moduleName,
4647
catalogURL: catalogURL
4748
)
4849
case .markdown:
49-
guard case .symbol(let symbolName) = MarkdownTitleFinder.find(parsing: snapshot.text) else {
50-
// This is an article that can be rendered on its own
51-
return try await documentationManager.renderDocCDocumentation(
52-
markupFile: snapshot.text,
53-
moduleName: moduleName,
54-
catalogURL: catalogURL
55-
)
50+
return try await markdownDocumentation(
51+
for: snapshot,
52+
in: workspace,
53+
moduleName: moduleName,
54+
catalogURL: catalogURL
55+
)
56+
case .swift:
57+
guard let position = req.position else {
58+
throw ResponseError.invalidParams("A position must be provided for Swift files")
5659
}
57-
guard let moduleName, symbolName == moduleName else {
58-
// This is a symbol extension page. Find the symbol so that we can include it in the request.
59-
guard let index = workspace.index(checkedFor: .deletedFiles) else {
60-
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
61-
}
62-
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
63-
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
64-
ofDocCSymbolLink: symbolLink,
65-
fetchSymbolGraph: { location in
66-
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
67-
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
68-
}
69-
let languageService = try await sourceKitLSPServer.primaryLanguageService(
70-
for: location.documentUri,
71-
.swift,
72-
in: symbolWorkspace
73-
)
74-
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)
60+
61+
return try await swiftDocumentation(
62+
for: snapshot,
63+
at: position,
64+
in: workspace,
65+
moduleName: moduleName,
66+
catalogURL: catalogURL
67+
)
68+
default:
69+
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
70+
}
71+
}
72+
73+
private func tutorialDocumentation(
74+
for snapshot: DocumentSnapshot,
75+
in workspace: Workspace,
76+
moduleName: String?,
77+
catalogURL: URL?
78+
) async throws -> DoccDocumentationResponse {
79+
return try await workspace.doccDocumentationManager.renderDocCDocumentation(
80+
tutorialFile: snapshot.text,
81+
moduleName: moduleName,
82+
catalogURL: catalogURL
83+
)
84+
}
85+
86+
private func markdownDocumentation(
87+
for snapshot: DocumentSnapshot,
88+
in workspace: Workspace,
89+
moduleName: String?,
90+
catalogURL: URL?
91+
) async throws -> DoccDocumentationResponse {
92+
guard let sourceKitLSPServer else {
93+
throw ResponseError.internalError("SourceKit-LSP is shutting down")
94+
}
95+
let documentationManager = workspace.doccDocumentationManager
96+
guard case .symbol(let symbolName) = MarkdownTitleFinder.find(parsing: snapshot.text) else {
97+
// This is an article that can be rendered on its own
98+
return try await documentationManager.renderDocCDocumentation(
99+
markupFile: snapshot.text,
100+
moduleName: moduleName,
101+
catalogURL: catalogURL
102+
)
103+
}
104+
guard let moduleName, symbolName == moduleName else {
105+
// This is a symbol extension page. Find the symbol so that we can include it in the request.
106+
guard let index = workspace.index(checkedFor: .deletedFiles) else {
107+
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
108+
}
109+
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
110+
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
111+
ofDocCSymbolLink: symbolLink,
112+
fetchSymbolGraph: { location in
113+
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
114+
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
75115
}
76-
)
77-
else {
78-
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
79-
}
80-
let symbolDocumentUri = symbolOccurrence.location.documentUri
81-
guard let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri) else {
82-
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
83-
}
84-
let languageService = try await sourceKitLSPServer.primaryLanguageService(
85-
for: symbolDocumentUri,
86-
.swift,
87-
in: symbolWorkspace
88-
)
89-
let symbolGraph = try await languageService.symbolGraph(
90-
forOnDiskContentsOf: symbolDocumentUri,
91-
at: symbolOccurrence.location
92-
)
93-
guard let symbolGraph else {
94-
throw ResponseError.internalError("Unable to retrieve symbol graph for \(symbolOccurrence.symbol.name)")
95-
}
96-
return try await documentationManager.renderDocCDocumentation(
97-
symbolUSR: symbolOccurrence.symbol.usr,
98-
symbolGraph: symbolGraph,
99-
markupFile: snapshot.text,
100-
moduleName: moduleName,
101-
catalogURL: catalogURL
116+
let languageService = try await sourceKitLSPServer.primaryLanguageService(
117+
for: location.documentUri,
118+
.swift,
119+
in: symbolWorkspace
120+
)
121+
return try await languageService.symbolGraph(
122+
forOnDiskContentsOf: location.documentUri,
123+
at: location
124+
)
125+
}
102126
)
127+
else {
128+
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
103129
}
104-
// This is a page representing the module itself.
105-
// Create a dummy symbol graph and tell SwiftDocC to convert the module name.
106-
// The version information isn't really all that important since we're creating
107-
// what is essentially an empty symbol graph.
130+
let symbolDocumentUri = symbolOccurrence.location.documentUri
131+
guard let symbolWorkspace = try await workspaceForDocument(uri: symbolDocumentUri) else {
132+
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
133+
}
134+
let languageService = try await sourceKitLSPServer.primaryLanguageService(
135+
for: symbolDocumentUri,
136+
.swift,
137+
in: symbolWorkspace
138+
)
139+
let symbolGraph = try await languageService.symbolGraph(
140+
forOnDiskContentsOf: symbolDocumentUri,
141+
at: symbolOccurrence.location
142+
)
108143
return try await documentationManager.renderDocCDocumentation(
109-
symbolUSR: moduleName,
110-
symbolGraph: emptySymbolGraph(forModule: moduleName),
144+
symbolUSR: symbolOccurrence.symbol.usr,
145+
symbolGraph: symbolGraph,
111146
markupFile: snapshot.text,
112147
moduleName: moduleName,
113148
catalogURL: catalogURL
114149
)
115-
default:
116-
throw ResponseError.requestFailed(doccDocumentationError: .unsupportedLanguage(snapshot.language))
117150
}
151+
// This is a page representing the module itself.
152+
// Create a dummy symbol graph and tell SwiftDocC to convert the module name.
153+
// The version information isn't really all that important since we're creating
154+
// what is essentially an empty symbol graph.
155+
return try await documentationManager.renderDocCDocumentation(
156+
symbolUSR: moduleName,
157+
symbolGraph: emptySymbolGraph(forModule: moduleName),
158+
markupFile: snapshot.text,
159+
moduleName: moduleName,
160+
catalogURL: catalogURL
161+
)
162+
}
163+
164+
private func swiftDocumentation(
165+
for snapshot: DocumentSnapshot,
166+
at position: Position,
167+
in workspace: Workspace,
168+
moduleName: String?,
169+
catalogURL: URL?
170+
) async throws -> DoccDocumentationResponse {
171+
guard let sourceKitLSPServer else {
172+
throw ResponseError.internalError("SourceKit-LSP is shutting down")
173+
}
174+
let documentationManager = workspace.doccDocumentationManager
175+
let (symbolGraph, symbolUSR, overrideDocComments) = try await sourceKitLSPServer.primaryLanguageService(
176+
for: snapshot.uri,
177+
snapshot.language,
178+
in: workspace
179+
).symbolGraph(for: snapshot, at: position)
180+
// Locate the documentation extension and include it in the request if one exists
181+
let markupExtensionFile = await orLog("Finding markup extension file for symbol \(symbolUSR)") {
182+
try await findMarkupExtensionFile(
183+
workspace: workspace,
184+
documentationManager: documentationManager,
185+
catalogURL: catalogURL,
186+
for: symbolUSR,
187+
fetchSymbolGraph: { location in
188+
guard let symbolWorkspace = try await workspaceForDocument(uri: location.documentUri) else {
189+
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
190+
}
191+
let languageService = try await sourceKitLSPServer.primaryLanguageService(
192+
for: location.documentUri,
193+
.swift,
194+
in: symbolWorkspace
195+
)
196+
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)
197+
}
198+
)
199+
}
200+
return try await documentationManager.renderDocCDocumentation(
201+
symbolUSR: symbolUSR,
202+
symbolGraph: symbolGraph,
203+
overrideDocComments: overrideDocComments,
204+
markupFile: markupExtensionFile,
205+
moduleName: moduleName,
206+
catalogURL: catalogURL
207+
)
208+
}
209+
210+
private func findMarkupExtensionFile(
211+
workspace: Workspace,
212+
documentationManager: DocCDocumentationManager,
213+
catalogURL: URL?,
214+
for symbolUSR: String,
215+
fetchSymbolGraph: @Sendable (SymbolLocation) async throws -> String?
216+
) async throws -> String? {
217+
guard let catalogURL else {
218+
return nil
219+
}
220+
let catalogIndex = try await documentationManager.catalogIndex(for: catalogURL)
221+
guard let index = workspace.index(checkedFor: .deletedFiles),
222+
let symbolInformation = try await index.doccSymbolInformation(
223+
ofUSR: symbolUSR,
224+
fetchSymbolGraph: fetchSymbolGraph
225+
),
226+
let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation)
227+
else {
228+
return nil
229+
}
230+
return try? documentManager.latestSnapshotOrDisk(
231+
DocumentURI(markupExtensionFileURL),
232+
language: .markdown
233+
)?.text
118234
}
119235
}
120236

Sources/DocumentationLanguageService/DocumentationLanguageService.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,15 @@ package actor DocumentationLanguageService: LanguageService, Sendable {
136136
package func symbolGraph(
137137
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
138138
at location: SymbolLocation
139-
) async throws -> String? {
140-
return nil
139+
) async throws -> String {
140+
throw ResponseError.internalError("Not applicable")
141+
}
142+
143+
package func symbolGraph(
144+
for snapshot: SourceKitLSP.DocumentSnapshot,
145+
at position: LanguageServerProtocol.Position
146+
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String]) {
147+
throw ResponseError.internalError("Not applicable")
141148
}
142149

143150
package func openGeneratedInterface(

Sources/InProcessClient/LanguageServiceRegistry+staticallyKnownServices.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension LanguageServiceRegistry {
2626
registry.register(ClangLanguageService.self, for: [.c, .cpp, .objective_c, .objective_cpp])
2727
registry.register(SwiftLanguageService.self, for: [.swift])
2828
#if canImport(DocumentationLanguageService)
29-
registry.register(DocumentationLanguageService.self, for: [.markdown, .tutorial])
29+
registry.register(DocumentationLanguageService.self, for: [.markdown, .tutorial, .swift])
3030
#endif
3131
return registry
3232
}()

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,19 @@ package protocol LanguageService: AnyObject, Sendable {
179179
func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse
180180
func symbolInfo(_ request: SymbolInfoRequest) async throws -> [SymbolDetails]
181181

182+
/// Retrieve the symbol graph for the given position in the given snapshot, including the USR of the symbol at the
183+
/// given position and the doc comments of the symbol at that position.
184+
func symbolGraph(
185+
for snapshot: DocumentSnapshot,
186+
at position: Position
187+
) async throws -> (symbolGraph: String, usr: String, overrideDocComments: [String])
188+
182189
/// Return the symbol graph at the given location for the contents of the document as they are on-disk (opposed to the
183190
/// in-memory modified version of the document).
184191
func symbolGraph(
185192
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
186193
at location: SymbolLocation
187-
) async throws -> String?
194+
) async throws -> String
188195

189196
/// Request a generated interface of a module to display in the IDE.
190197
///

0 commit comments

Comments
 (0)