Skip to content

Commit 47ca76b

Browse files
authored
Merge pull request #2226 from matthewbastien/docc-cache-snapshots
[DocC Live Preview] Cache on-disk snapshots opened in sourcekitd
2 parents 17d2605 + 58096eb commit 47ca76b

File tree

12 files changed

+246
-178
lines changed

12 files changed

+246
-178
lines changed

Sources/BuildServerIntegration/BuildServerManager.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,28 @@ package actor BuildServerManager: QueueBasedMessageHandler {
795795
return languageFromBuildServer ?? Language(inferredFromFileExtension: document)
796796
}
797797

798+
/// Returns the language that a document should be interpreted in for background tasks where the editor doesn't
799+
/// specify the document's language.
800+
///
801+
/// If the language could not be determined, this method throws an error.
802+
package func defaultLanguageInCanonicalTarget(for document: DocumentURI) async throws -> Language {
803+
struct UnableToInferLanguage: Error, CustomStringConvertible {
804+
let document: DocumentURI
805+
var description: String { "Unable to infer language for \(document)" }
806+
}
807+
808+
guard let canonicalTarget = await self.canonicalTarget(for: document) else {
809+
guard let language = Language(inferredFromFileExtension: document) else {
810+
throw UnableToInferLanguage(document: document)
811+
}
812+
return language
813+
}
814+
guard let language = await defaultLanguage(for: document, in: canonicalTarget) else {
815+
throw UnableToInferLanguage(document: document)
816+
}
817+
return language
818+
}
819+
798820
/// Retrieve information about the given source file within the build server.
799821
package func sourceFileInfo(for document: DocumentURI) async -> SourceFileInfo? {
800822
return await orLog("Getting targets for source file") {

Sources/ClangLanguageService/ClangLanguageService.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,14 +496,10 @@ extension ClangLanguageService {
496496
}
497497

498498
package func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
499-
guard let sourceKitLSPServer else {
500-
throw ResponseError.unknown("Connection to the editor closed")
499+
guard let language = openDocuments[req.textDocument.uri] else {
500+
throw ResponseError.requestFailed("Documentation preview is not available for clang files")
501501
}
502-
503-
let snapshot = try sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri)
504-
throw ResponseError.requestFailed(
505-
"Documentation preview is not available for \(snapshot.language.description) files"
506-
)
502+
throw ResponseError.requestFailed("Documentation preview is not available for \(language.description) files")
507503
}
508504

509505
package func symbolInfo(_ req: SymbolInfoRequest) async throws -> [SymbolDetails] {

Sources/DocumentationLanguageService/DoccDocumentationError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ enum DocCDocumentationError: LocalizedError {
2323
case .unsupportedLanguage(let language):
2424
return "Documentation preview is not available for \(language.description) files"
2525
case .indexNotAvailable:
26-
return "The index is not availble to complete the request"
26+
return "The index is not available to complete the request"
2727
case .symbolNotFound(let symbolName):
2828
return "Could not find symbol \(symbolName) in the project"
2929
}

Sources/DocumentationLanguageService/DoccDocumentationHandler.swift

Lines changed: 56 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -104,47 +104,35 @@ extension DocumentationLanguageService {
104104
guard let index = workspace.index(checkedFor: .deletedFiles) else {
105105
throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable)
106106
}
107-
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
108-
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
109-
ofDocCSymbolLink: symbolLink,
110-
fetchSymbolGraph: { location in
111-
guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: location.documentUri) else {
112-
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
107+
return try await sourceKitLSPServer.withOnDiskDocumentManager { onDiskDocumentManager in
108+
guard let symbolLink = DocCSymbolLink(linkString: symbolName),
109+
let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence(
110+
ofDocCSymbolLink: symbolLink,
111+
fetchSymbolGraph: { location in
112+
return try await sourceKitLSPServer.primaryLanguageService(
113+
for: location.documentUri,
114+
workspace.buildServerManager.defaultLanguageInCanonicalTarget(for: location.documentUri),
115+
in: workspace
116+
)
117+
.symbolGraph(forOnDiskContentsAt: location, in: workspace, manager: onDiskDocumentManager)
113118
}
114-
let languageService = try await sourceKitLSPServer.primaryLanguageService(
115-
for: location.documentUri,
116-
.swift,
117-
in: symbolWorkspace
118-
)
119-
return try await languageService.symbolGraph(
120-
forOnDiskContentsOf: location.documentUri,
121-
at: location
122-
)
123-
}
119+
)
120+
else {
121+
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
122+
}
123+
let symbolGraph = try await sourceKitLSPServer.primaryLanguageService(
124+
for: symbolOccurrence.location.documentUri,
125+
workspace.buildServerManager.defaultLanguageInCanonicalTarget(for: symbolOccurrence.location.documentUri),
126+
in: workspace
127+
).symbolGraph(forOnDiskContentsAt: symbolOccurrence.location, in: workspace, manager: onDiskDocumentManager)
128+
return try await documentationManager.renderDocCDocumentation(
129+
symbolUSR: symbolOccurrence.symbol.usr,
130+
symbolGraph: symbolGraph,
131+
markupFile: snapshot.text,
132+
moduleName: moduleName,
133+
catalogURL: catalogURL
124134
)
125-
else {
126-
throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName))
127135
}
128-
let symbolDocumentUri = symbolOccurrence.location.documentUri
129-
guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: symbolDocumentUri) else {
130-
throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)")
131-
}
132-
let languageService = try await sourceKitLSPServer.primaryLanguageService(
133-
for: symbolDocumentUri,
134-
.swift,
135-
in: symbolWorkspace
136-
)
137-
let symbolGraph = try await languageService.symbolGraph(
138-
forOnDiskContentsOf: symbolDocumentUri,
139-
at: symbolOccurrence.location
140-
)
141-
return try await documentationManager.renderDocCDocumentation(
142-
symbolUSR: symbolOccurrence.symbol.usr,
143-
symbolGraph: symbolGraph,
144-
markupFile: snapshot.text,
145-
moduleName: moduleName,
146-
catalogURL: catalogURL
147-
)
148136
}
149137
// This is a page representing the module itself.
150138
// Create a dummy symbol graph and tell SwiftDocC to convert the module name.
@@ -175,24 +163,26 @@ extension DocumentationLanguageService {
175163
in: workspace
176164
).symbolGraph(for: snapshot, at: position)
177165
// Locate the documentation extension and include it in the request if one exists
178-
let markupExtensionFile = await orLog("Finding markup extension file for symbol \(symbolUSR)") {
179-
try await findMarkupExtensionFile(
180-
workspace: workspace,
181-
documentationManager: documentationManager,
182-
catalogURL: catalogURL,
183-
for: symbolUSR,
184-
fetchSymbolGraph: { location in
185-
guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: location.documentUri) else {
186-
throw ResponseError.internalError("Unable to find language service for \(location.documentUri)")
166+
let markupExtensionFile = await sourceKitLSPServer.withOnDiskDocumentManager {
167+
[documentationManager, documentManager = try documentManager] onDiskDocumentManager in
168+
await orLog("Finding markup extension file for symbol \(symbolUSR)") {
169+
try await Self.findMarkupExtensionFile(
170+
workspace: workspace,
171+
documentationManager: documentationManager,
172+
documentManager: documentManager,
173+
catalogURL: catalogURL,
174+
for: symbolUSR,
175+
fetchSymbolGraph: { location in
176+
try await sourceKitLSPServer.primaryLanguageService(
177+
for: location.documentUri,
178+
snapshot.language,
179+
in: workspace
180+
)
181+
.symbolGraph(forOnDiskContentsAt: location, in: workspace, manager: onDiskDocumentManager)
182+
187183
}
188-
let languageService = try await sourceKitLSPServer.primaryLanguageService(
189-
for: location.documentUri,
190-
.swift,
191-
in: symbolWorkspace
192-
)
193-
return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location)
194-
}
195-
)
184+
)
185+
}
196186
}
197187
return try await documentationManager.renderDocCDocumentation(
198188
symbolUSR: symbolUSR,
@@ -204,9 +194,10 @@ extension DocumentationLanguageService {
204194
)
205195
}
206196

207-
private func findMarkupExtensionFile(
197+
private static func findMarkupExtensionFile(
208198
workspace: Workspace,
209199
documentationManager: DocCDocumentationManager,
200+
documentManager: DocumentManager,
210201
catalogURL: URL?,
211202
for symbolUSR: String,
212203
fetchSymbolGraph: @Sendable (SymbolLocation) async throws -> String?
@@ -215,16 +206,17 @@ extension DocumentationLanguageService {
215206
return nil
216207
}
217208
let catalogIndex = try await documentationManager.catalogIndex(for: catalogURL)
218-
guard let index = workspace.index(checkedFor: .deletedFiles),
219-
let symbolInformation = try await index.doccSymbolInformation(
220-
ofUSR: symbolUSR,
221-
fetchSymbolGraph: fetchSymbolGraph
222-
),
223-
let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation)
224-
else {
209+
guard let index = workspace.index(checkedFor: .deletedFiles) else {
210+
return nil
211+
}
212+
let symbolInformation = try await index.doccSymbolInformation(
213+
ofUSR: symbolUSR,
214+
fetchSymbolGraph: fetchSymbolGraph
215+
)
216+
guard let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation) else {
225217
return nil
226218
}
227-
return try? documentManager.latestSnapshotOrDisk(
219+
return documentManager.latestSnapshotOrDisk(
228220
DocumentURI(markupExtensionFileURL),
229221
language: .markdown
230222
)?.text

Sources/DocumentationLanguageService/IndexStoreDB+Extensions.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension CheckedIndex {
4040
var result: [SymbolOccurrence] = []
4141
for occurrence in topLevelSymbolOccurrences {
4242
let info = try await doccSymbolInformation(ofUSR: occurrence.symbol.usr, fetchSymbolGraph: fetchSymbolGraph)
43-
if let info, info.matches(symbolLink) {
43+
if info.matches(symbolLink) {
4444
result.append(occurrence)
4545
}
4646
}
@@ -60,9 +60,9 @@ extension CheckedIndex {
6060
func doccSymbolInformation(
6161
ofUSR usr: String,
6262
fetchSymbolGraph: (SymbolLocation) async throws -> String?
63-
) async throws -> DocCSymbolInformation? {
63+
) async throws -> DocCSymbolInformation {
6464
guard let topLevelSymbolOccurrence = primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
65-
return nil
65+
throw DocCCheckedIndexError.emptyDocCSymbolLink
6666
}
6767
let moduleName = topLevelSymbolOccurrence.location.moduleName
6868
var symbols = [topLevelSymbolOccurrence]

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_library(SourceKitLSP STATIC
1313
LogMessageNotification+representingStructureUsingEmojiPrefixIfNecessary.swift
1414
MacroExpansionReferenceDocumentURLData.swift
1515
MessageHandlingDependencyTracker.swift
16+
OnDiskDocumentManager.swift
1617
ReferenceDocumentURL.swift
1718
Rename.swift
1819
SemanticTokensLegend+SourceKitLSPLegend.swift

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
package import BuildServerIntegration
1314
import Foundation
1415
package import IndexStoreDB
1516
package import LanguageServerProtocol
@@ -148,6 +149,18 @@ package protocol LanguageService: AnyObject, Sendable {
148149
/// Sent to close a document on the Language Server.
149150
func closeDocument(_ notification: DidCloseTextDocumentNotification) async
150151

152+
/// Sent to open up a document on the Language Server whose contents are on-disk.
153+
///
154+
/// The snapshot will have a synthesized name and the caller is responsible for synthesizing build settings for it.
155+
///
156+
/// - Important: This should only be called by `OnDiskDocumentManager`.
157+
func openOnDiskDocument(snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) async throws
158+
159+
/// Sent to close a document that was opened by `openOnDiskDocument`.
160+
///
161+
/// - Important: This should only be called by `OnDiskDocumentManager`.
162+
func closeOnDiskDocument(uri: DocumentURI) async throws
163+
151164
/// Re-open the given document, discarding any in-memory state and forcing an AST to be re-built after build settings
152165
/// have been changed. This needs to be handled via a notification to ensure that no other request for this document
153166
/// is executing at the same time.
@@ -197,8 +210,9 @@ package protocol LanguageService: AnyObject, Sendable {
197210
/// Return the symbol graph at the given location for the contents of the document as they are on-disk (opposed to the
198211
/// in-memory modified version of the document).
199212
func symbolGraph(
200-
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
201-
at location: SymbolLocation
213+
forOnDiskContentsAt location: SymbolLocation,
214+
in workspace: Workspace,
215+
manager: OnDiskDocumentManager
202216
) async throws -> String
203217

204218
/// Request a generated interface of a module to display in the IDE.
@@ -330,6 +344,14 @@ package extension LanguageService {
330344

331345
func clientInitialized(_ initialized: LanguageServerProtocol.InitializedNotification) async {}
332346

347+
func openOnDiskDocument(snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) async throws {
348+
throw ResponseError.unknown("\(#function) not implemented in \(Self.self)")
349+
}
350+
351+
func closeOnDiskDocument(uri: DocumentURI) async throws {
352+
throw ResponseError.unknown("\(#function) not implemented in \(Self.self)")
353+
}
354+
333355
func willSaveDocument(_ notification: WillSaveTextDocumentNotification) async {}
334356

335357
func didSaveDocument(_ notification: DidSaveTextDocumentNotification) async {}
@@ -368,8 +390,9 @@ package extension LanguageService {
368390
}
369391

370392
func symbolGraph(
371-
forOnDiskContentsOf symbolDocumentUri: DocumentURI,
372-
at location: SymbolLocation
393+
forOnDiskContentsAt location: SymbolLocation,
394+
in workspace: Workspace,
395+
manager: OnDiskDocumentManager
373396
) async throws -> String {
374397
throw ResponseError.internalError("\(#function) not implemented in \(Self.self)")
375398
}

0 commit comments

Comments
 (0)