Skip to content

Commit b317928

Browse files
authored
Merge pull request #2247 from ahoppen/handle-swift-documentation-in-doclanguageservice
Handle documentation requests for Swift files in `DocumentationLanguageService`
2 parents 45d5bfb + 22fae21 commit b317928

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)