@@ -16,6 +16,7 @@ internal import IndexStoreDB
1616@_spi ( SourceKitLSP) import LanguageServerProtocol
1717@_spi ( SourceKitLSP) import SKLogging
1818import SemanticIndex
19+ import SourceKitD
1920import SourceKitLSP
2021import SwiftSyntax
2122import ToolchainRegistry
@@ -32,7 +33,7 @@ final class SwiftCodeLensScanner: SyntaxVisitor {
3233 /// The display name of the build target containing this document, if available.
3334 private let targetName : String ?
3435
35- /// The language service used to resolve cursor info for symbols .
36+ /// The language service used to resolve symbol metadata for code lenses .
3637 private let languageService : SwiftLanguageService
3738
3839 /// The map of supported commands and their client side command names.
@@ -86,10 +87,7 @@ final class SwiftCodeLensScanner: SyntaxVisitor {
8687 let syntaxTree = await syntaxTreeManager. syntaxTree ( for: snapshot)
8788 visitor. walk ( syntaxTree)
8889
89- // Process collected symbols asynchronously for reference counts
90- for (nameToken, displayRange) in visitor. symbolsToProcess {
91- await visitor. captureReferenceLens ( for: nameToken, at: displayRange)
92- }
90+ await visitor. captureReferenceLenses ( )
9391
9492 var codeLenses = visitor. result
9593
@@ -197,45 +195,51 @@ final class SwiftCodeLensScanner: SyntaxVisitor {
197195 }
198196 }
199197
200- /// Queries the index for the number of references to a symbol and appends a code lens with the count.
201- private func captureReferenceLens( for nameToken: TokenSyntax , at displayRange: Range < AbsolutePosition > ) async {
202- guard let referencesCommand = supportedCommands [ . references] else { return }
203-
204- let lensRange = snapshot. absolutePositionRange ( of: displayRange)
205- let nameRange = snapshot. absolutePositionRange ( of: nameToken. trimmedRange)
198+ /// Queries sourcekitd once for declaration USRs, then looks up reference counts in the index.
199+ private func captureReferenceLenses( ) async {
200+ guard let referencesCommand = supportedCommands [ . references] ,
201+ let index = await workspace? . index ( checkedFor: . deletedFiles)
202+ else {
203+ return
204+ }
206205
207206 do {
208- let cursorInfoResults = try await languageService. cursorInfo (
207+ let declarationUsrs = try await languageService. declarationUSRs (
209208 snapshot,
210- compileCommand: await languageService. compileCommand ( for: snapshot. uri, fallbackAfterTimeout: false ) ,
211- nameRange
209+ compileCommand: await languageService. compileCommand ( for: snapshot. uri, fallbackAfterTimeout: false )
210+ )
211+ let usrsByOffset = Dictionary (
212+ declarationUsrs. map { ( $0. offset, $0. usr) } ,
213+ uniquingKeysWith: { first, _ in first }
212214 )
213- . cursorInfo
214-
215- guard let cursorInfo = cursorInfoResults. first,
216- let usr = cursorInfo. symbolInfo. usr,
217- let index = await workspace? . index ( checkedFor: . deletedFiles)
218- else { return }
219-
220- var referenceCount = 0
221- index. forEachSymbolOccurrence ( byUSR: usr, roles: . reference) { _ in
222- referenceCount += 1
223- return true
224- }
225215
226- let title = " \( referenceCount) reference \( referenceCount == 1 ? " " : " s " ) "
227- result. append (
228- CodeLens (
229- range: lensRange,
230- command: Command (
231- title: title,
232- command: referencesCommand,
233- arguments: [ . string( snapshot. uri. stringValue) , nameRange. lowerBound. encodeToLSPAny ( ) ]
216+ for (nameToken, displayRange) in symbolsToProcess {
217+ guard let usr = usrsByOffset [ nameToken. trimmedRange. lowerBound. utf8Offset] else {
218+ continue
219+ }
220+
221+ var referenceCount = 0
222+ try index. forEachSymbolOccurrence ( byUSR: usr, roles: . reference) { _ in
223+ referenceCount += 1
224+ return true
225+ }
226+
227+ let lensRange = snapshot. absolutePositionRange ( of: displayRange)
228+ let nameRange = snapshot. absolutePositionRange ( of: nameToken. trimmedRange)
229+ let title = " \( referenceCount) reference \( referenceCount == 1 ? " " : " s " ) "
230+ result. append (
231+ CodeLens (
232+ range: lensRange,
233+ command: Command (
234+ title: title,
235+ command: referencesCommand,
236+ arguments: [ . string( snapshot. uri. stringValue) , nameRange. lowerBound. encodeToLSPAny ( ) ]
237+ )
234238 )
235239 )
236- )
240+ }
237241 } catch {
238- logger. info ( " Failed to get cursor info for reference count: \( error. forLogging, privacy: . public) " )
242+ logger. info ( " Failed to get declaration USRs for reference count: \( error. forLogging, privacy: . public) " )
239243 }
240244 }
241245
@@ -284,3 +288,51 @@ final class SwiftCodeLensScanner: SyntaxVisitor {
284288 }
285289 }
286290}
291+
292+ private struct DeclarationUSRInfo {
293+ let offset : Int
294+ let usr : String
295+ }
296+
297+ extension SwiftLanguageService {
298+ fileprivate func declarationUSRs(
299+ _ snapshot: DocumentSnapshot ,
300+ compileCommand: SwiftCompileCommand ? ,
301+ _ range: Range < Position > ? = nil
302+ ) async throws -> [ DeclarationUSRInfo ] {
303+ let skreq = sourcekitd. dictionary ( [
304+ keys. cancelOnSubsequentRequest: 0 ,
305+ keys. filePath: snapshot. uri. sourcekitdSourceFile,
306+ keys. compilerArgs: compileCommand? . compilerArgs as [ any SKDRequestValue ] ? ,
307+ ] )
308+
309+ if let range {
310+ let start = snapshot. utf8Offset ( of: range. lowerBound)
311+ let end = snapshot. utf8Offset ( of: range. upperBound)
312+ skreq. set ( keys. offset, to: start)
313+ skreq. set ( keys. length, to: end - start)
314+ }
315+
316+ let dict = try await send ( sourcekitdRequest: \. collectDeclarationUSR, skreq, snapshot: snapshot)
317+ guard let declarations: SKDResponseArray = dict [ keys. declarations] else {
318+ return [ ]
319+ }
320+
321+ var result : [ DeclarationUSRInfo ] = [ ]
322+ result. reserveCapacity ( declarations. count)
323+
324+ // swift-format-ignore: ReplaceForEachWithForLoop
325+ declarations. forEach { ( _, declaration) -> Bool in
326+ guard let offset: Int = declaration [ keys. offset] ,
327+ let usr: String = declaration [ keys. usr]
328+ else {
329+ assertionFailure ( " DeclarationUSRInfo failed to deserialize " )
330+ return true
331+ }
332+ result. append ( DeclarationUSRInfo ( offset: offset, usr: usr) )
333+ return true
334+ }
335+
336+ return result
337+ }
338+ }
0 commit comments