Skip to content

Commit 0927ce1

Browse files
committed
Add cache for fullDocumentDiagnosticReport
1 parent 8f53d08 commit 0927ce1

File tree

1 file changed

+51
-4
lines changed

1 file changed

+51
-4
lines changed

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ public actor SwiftLanguageServer: ToolchainLanguageServer {
139139

140140
private var stateChangeHandlers: [(_ oldState: LanguageServerState, _ newState: LanguageServerState) -> Void] = []
141141

142+
/// The cache that stores reports for snapshot ids
143+
///
144+
/// Conceptually, this is a dictionary. To prevent excessive memory usage we
145+
/// only keep `cacheSize` entries within the array. Older entries are at the
146+
/// end of the list, newer entries at the front.
147+
private var reportCache:
148+
[(
149+
snapshotID: DocumentSnapshot.ID,
150+
report: RelatedFullDocumentDiagnosticReport
151+
)] = []
152+
153+
/// The number of reports to keep
154+
///
155+
/// - Note: This has been chosen without scientific measurements.
156+
private let cacheSize = 5
157+
142158
/// Creates a language server for the given client using the sourcekitd dylib specified in `toolchain`.
143159
/// `reopenDocuments` is a closure that will be called if sourcekitd crashes and the `SwiftLanguageServer` asks its parent server to reopen all of its documents.
144160
/// Returns `nil` if `sourcektid` couldn't be found.
@@ -970,7 +986,8 @@ extension SwiftLanguageServer {
970986
let diagnosticReport = try await self.fullDocumentDiagnosticReport(
971987
DocumentDiagnosticsRequest(
972988
textDocument: params.textDocument
973-
)
989+
),
990+
useCache: true
974991
)
975992

976993
let codeActions = diagnosticReport.items.flatMap { (diag) -> [CodeAction] in
@@ -1065,9 +1082,13 @@ extension SwiftLanguageServer {
10651082
}
10661083

10671084
private func fullDocumentDiagnosticReport(
1068-
_ req: DocumentDiagnosticsRequest
1085+
_ req: DocumentDiagnosticsRequest,
1086+
useCache: Bool = false
10691087
) async throws -> RelatedFullDocumentDiagnosticReport {
10701088
let snapshot = try documentManager.latestSnapshot(req.textDocument.uri)
1089+
if useCache, let report = report(for: snapshot.id) {
1090+
return report
1091+
}
10711092
guard let buildSettings = await self.buildSettings(for: req.textDocument.uri), !buildSettings.isFallback else {
10721093
logger.log(
10731094
"Producing syntactic diagnostics from the built-in swift-syntax because we have fallback arguments"
@@ -1076,7 +1097,9 @@ extension SwiftLanguageServer {
10761097
// sourcekitd won't be able to give us accurate semantic diagnostics.
10771098
// Fall back to providing syntactic diagnostics from the built-in
10781099
// swift-syntax. That's the best we can do for now.
1079-
return try await syntacticDiagnosticFromBuiltInSwiftSyntax(for: snapshot)
1100+
let report = try await syntacticDiagnosticFromBuiltInSwiftSyntax(for: snapshot)
1101+
setReport(for: snapshot.id, report: report)
1102+
return report
10801103
}
10811104

10821105
try Task.checkCancellation()
@@ -1109,7 +1132,31 @@ extension SwiftLanguageServer {
11091132
return true
11101133
}
11111134

1112-
return RelatedFullDocumentDiagnosticReport(items: diagnostics)
1135+
let report = RelatedFullDocumentDiagnosticReport(items: diagnostics)
1136+
setReport(for: snapshot.id, report: report)
1137+
return report
1138+
}
1139+
1140+
/// The report for the given document snapshot.
1141+
private func report(for snapshotID: DocumentSnapshot.ID) -> RelatedFullDocumentDiagnosticReport? {
1142+
return reportCache.first(where: { $0.snapshotID == snapshotID })?.report
1143+
}
1144+
1145+
/// Set the report for the given document snapshot.
1146+
///
1147+
/// If we are already storing `cacheSize` many reports, the oldest one
1148+
/// will get discarded.
1149+
private func setReport(for snapshotID: DocumentSnapshot.ID, report: RelatedFullDocumentDiagnosticReport) {
1150+
reportCache.insert((snapshotID, report), at: 0)
1151+
1152+
// Remove any reports for old versions of this document.
1153+
reportCache.removeAll(where: { $0.snapshotID < snapshotID })
1154+
1155+
// If we still have more than `cacheSize` reports, delete the ones that
1156+
// were produced last. We can always re-request them on-demand.
1157+
while reportCache.count > cacheSize {
1158+
reportCache.removeLast()
1159+
}
11131160
}
11141161

11151162
public func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? {

0 commit comments

Comments
 (0)