Skip to content

Commit 7572fb9

Browse files
committed
Report secondary cursor info results
rdar://119163908
1 parent e4c2a3f commit 7572fb9

File tree

8 files changed

+242
-111
lines changed

8 files changed

+242
-111
lines changed

Sources/SKTestSupport/IndexedSingleSwiftFileWorkspace.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ public struct IndexedSingleSwiftFileWorkspace {
3131
///
3232
/// - Parameters:
3333
/// - markedText: The contents of the source file including location markers.
34+
/// - allowBuildFailure: Whether to fail if the input source file fails to build or whether to continue the test
35+
/// even if the input source is invalid.
3436
/// - workspaceDirectory: If specified, the temporary files will be put in this directory. If `nil` a temporary
3537
/// scratch directory will be created based on `testName`.
3638
/// - cleanUp: Whether to remove the temporary directory when the SourceKit-LSP server shuts down.
3739
public init(
3840
_ markedText: String,
3941
indexSystemModules: Bool = false,
42+
allowBuildFailure: Bool = false,
4043
workspaceDirectory: URL? = nil,
4144
cleanUp: Bool = cleanScratchDirectories,
4245
testName: String = #function
@@ -83,7 +86,13 @@ public struct IndexedSingleSwiftFileWorkspace {
8386
)
8487

8588
// Run swiftc to build the index store
86-
try await Process.checkNonZeroExit(arguments: [swiftc.path] + compilerArguments)
89+
do {
90+
try await Process.checkNonZeroExit(arguments: [swiftc.path] + compilerArguments)
91+
} catch {
92+
if !allowBuildFailure {
93+
throw error
94+
}
95+
}
8796

8897
// Create the test client
8998
var options = SourceKitServer.Options.testDefault

Sources/SourceKitD/sourcekitd_uids.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public struct sourcekitd_keys {
7979
public let request: sourcekitd_uid_t
8080
public let results: sourcekitd_uid_t
8181
public let retrieve_refactor_actions: sourcekitd_uid_t
82+
public let secondarySymbols: sourcekitd_uid_t
8283
public let semantic_tokens: sourcekitd_uid_t
8384
public let severity: sourcekitd_uid_t
8485
public let sourceEditKindActive: sourcekitd_uid_t
@@ -186,6 +187,7 @@ public struct sourcekitd_keys {
186187
request = api.uid_get_from_cstr("key.request")!
187188
results = api.uid_get_from_cstr("key.results")!
188189
retrieve_refactor_actions = api.uid_get_from_cstr("key.retrieve_refactor_actions")!
190+
secondarySymbols = api.uid_get_from_cstr("key.secondary_symbols")!
189191
semantic_tokens = api.uid_get_from_cstr("key.semantic_tokens")!
190192
severity = api.uid_get_from_cstr("key.severity")!
191193
sourceEditKindActive = api.uid_get_from_cstr("source.edit.kind.active")!

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,49 +1706,24 @@ extension SourceKitServer {
17061706
return try await languageService.declaration(req)
17071707
}
17081708

1709-
/// Returns the result of a `DefinitionRequest` by running a `SymbolInfoRequest`, inspecting
1710-
/// its result and doing index lookups, if necessary.
1711-
///
1712-
/// In contrast to `definition`, this does not fall back to sending a `DefinitionRequest` to the
1713-
/// toolchain language server.
1714-
private func indexBasedDefinition(
1715-
_ req: DefinitionRequest,
1716-
workspace: Workspace,
1709+
/// Return the locations for jump to definition from the given `SymbolDetails`.
1710+
private func definitionLocations(
1711+
for symbol: SymbolDetails,
1712+
in uri: DocumentURI,
17171713
languageService: ToolchainLanguageServer
17181714
) async throws -> [Location] {
1719-
let symbols = try await languageService.symbolInfo(
1720-
SymbolInfoRequest(
1721-
textDocument: req.textDocument,
1722-
position: req.position
1723-
)
1724-
)
1725-
guard let symbol = symbols.first else {
1726-
return []
1727-
}
1728-
1729-
if let bestLocalDeclaration = symbol.bestLocalDeclaration,
1730-
!(symbol.isDynamic ?? true),
1731-
symbol.usr?.hasPrefix("s:") ?? false /* Swift symbols have USRs starting with s: */
1732-
{
1733-
// If we have a known non-dynamic symbol within Swift, we don't need to do an index lookup.
1734-
// For non-Swift symbols, we need to perform an index lookup because the best local declaration will point to
1735-
// a header file but jump-to-definition should prefer the implementation (there's the declaration request to jump
1736-
// to the function's declaration).
1737-
return [bestLocalDeclaration]
1738-
}
1739-
17401715
// If this symbol is a module then generate a textual interface
17411716
if symbol.kind == .module, let name = symbol.name {
17421717
let interfaceLocation = try await self.definitionInInterface(
1743-
req,
17441718
moduleName: name,
17451719
symbolUSR: nil,
1720+
originatorUri: uri,
17461721
languageService: languageService
17471722
)
17481723
return [interfaceLocation]
17491724
}
17501725

1751-
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
1726+
guard let index = await self.workspaceForDocument(uri: uri)?.index else {
17521727
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
17531728
return [bestLocalDeclaration]
17541729
} else {
@@ -1797,16 +1772,46 @@ extension SourceKitServer {
17971772
if URL(fileURLWithPath: occurrence.location.path).pathExtension == "swiftinterface" {
17981773
// If the location is in `.swiftinterface` file, use moduleName to return textual interface.
17991774
return try await self.definitionInInterface(
1800-
req,
18011775
moduleName: occurrence.location.moduleName,
18021776
symbolUSR: occurrence.symbol.usr,
1777+
originatorUri: uri,
18031778
languageService: languageService
18041779
)
18051780
}
18061781
return indexToLSPLocation(occurrence.location)
18071782
}
18081783
}
18091784

1785+
/// Returns the result of a `DefinitionRequest` by running a `SymbolInfoRequest`, inspecting
1786+
/// its result and doing index lookups, if necessary.
1787+
///
1788+
/// In contrast to `definition`, this does not fall back to sending a `DefinitionRequest` to the
1789+
/// toolchain language server.
1790+
private func indexBasedDefinition(
1791+
_ req: DefinitionRequest,
1792+
workspace: Workspace,
1793+
languageService: ToolchainLanguageServer
1794+
) async throws -> [Location] {
1795+
let symbols = try await languageService.symbolInfo(
1796+
SymbolInfoRequest(
1797+
textDocument: req.textDocument,
1798+
position: req.position
1799+
)
1800+
)
1801+
return try await symbols.asyncMap { (symbol) -> [Location] in
1802+
if symbol.bestLocalDeclaration != nil && !(symbol.isDynamic ?? true)
1803+
&& symbol.usr?.hasPrefix("s:") ?? false /* Swift symbols have USRs starting with s: */
1804+
{
1805+
// If we have a known non-dynamic symbol within Swift, we don't need to do an index lookup.
1806+
// For non-Swift symbols, we need to perform an index lookup because the best local declaration will point to
1807+
// a header file but jump-to-definition should prefer the implementation (there's the declaration request to jump
1808+
// to the function's declaration).
1809+
return [symbol.bestLocalDeclaration].compactMap { $0 }
1810+
}
1811+
return try await self.definitionLocations(for: symbol, in: req.textDocument.uri, languageService: languageService)
1812+
}.flatMap { $0 }
1813+
}
1814+
18101815
func definition(
18111816
_ req: DefinitionRequest,
18121817
workspace: Workspace,
@@ -1827,13 +1832,22 @@ extension SourceKitServer {
18271832
return .locations(indexBasedResponse)
18281833
}
18291834

1835+
/// Generate the generated interface for the given module, write it to disk and return the location to which to jump
1836+
/// to get to the definition of `symbolUSR`.
1837+
///
1838+
/// `originatorUri` is the URI of the file from which the definition request is performed. It is used to determine the
1839+
/// compiler arguments to generate the generated inteface.
18301840
func definitionInInterface(
1831-
_ req: DefinitionRequest,
18321841
moduleName: String,
18331842
symbolUSR: String?,
1843+
originatorUri: DocumentURI,
18341844
languageService: ToolchainLanguageServer
18351845
) async throws -> Location {
1836-
let openInterface = OpenInterfaceRequest(textDocument: req.textDocument, name: moduleName, symbolUSR: symbolUSR)
1846+
let openInterface = OpenInterfaceRequest(
1847+
textDocument: TextDocumentIdentifier(originatorUri),
1848+
name: moduleName,
1849+
symbolUSR: symbolUSR
1850+
)
18371851
guard let interfaceDetails = try await languageService.openInterface(openInterface) else {
18381852
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
18391853
}

Sources/SourceKitLSP/Swift/CursorInfo.swift

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,49 @@ struct CursorInfo {
3838
init(
3939
_ symbolInfo: SymbolDetails,
4040
annotatedDeclaration: String?,
41-
documentationXML: String?,
42-
refactorActions: [SemanticRefactorCommand]? = nil
41+
documentationXML: String?
4342
) {
4443
self.symbolInfo = symbolInfo
4544
self.annotatedDeclaration = annotatedDeclaration
4645
self.documentationXML = documentationXML
47-
self.refactorActions = refactorActions
46+
}
47+
48+
init?(
49+
_ dict: SKDResponseDictionary,
50+
sourcekitd: some SourceKitD
51+
) {
52+
let keys = sourcekitd.keys
53+
guard let kind: sourcekitd_uid_t = dict[keys.kind] else {
54+
// Nothing to report.
55+
return nil
56+
}
57+
58+
var location: Location? = nil
59+
if let filepath: String = dict[keys.filepath],
60+
let line: Int = dict[keys.line],
61+
let column: Int = dict[keys.column]
62+
{
63+
let position = Position(
64+
line: line - 1,
65+
// FIXME: we need to convert the utf8/utf16 column, which may require reading the file!
66+
utf16index: column - 1
67+
)
68+
location = Location(uri: DocumentURI(URL(fileURLWithPath: filepath)), range: Range(position))
69+
}
70+
71+
self.init(
72+
SymbolDetails(
73+
name: dict[keys.name],
74+
containerName: nil,
75+
usr: dict[keys.usr],
76+
bestLocalDeclaration: location,
77+
kind: kind.asSymbolKind(sourcekitd.values),
78+
isDynamic: dict[keys.isDynamic] ?? false,
79+
receiverUsrs: dict[keys.receivers]?.compactMap { $0[keys.usr] as String? } ?? []
80+
),
81+
annotatedDeclaration: dict[keys.annotated_decl],
82+
documentationXML: dict[keys.doc_full_as_xml]
83+
)
4884
}
4985
}
5086

@@ -83,7 +119,7 @@ extension SwiftLanguageServer {
83119
_ uri: DocumentURI,
84120
_ range: Range<Position>,
85121
additionalParameters appendAdditionalParameters: ((SKDRequestDictionary) -> Void)? = nil
86-
) async throws -> CursorInfo? {
122+
) async throws -> (cursorInfo: [CursorInfo], refactorActions: [SemanticRefactorCommand]) {
87123
let snapshot = try documentManager.latestSnapshot(uri)
88124

89125
guard let offsetRange = snapshot.utf8OffsetRange(of: range) else {
@@ -110,48 +146,19 @@ extension SwiftLanguageServer {
110146

111147
let dict = try await self.sourcekitd.send(skreq, fileContents: snapshot.text)
112148

113-
guard let kind: sourcekitd_uid_t = dict[keys.kind] else {
114-
// Nothing to report.
115-
return nil
149+
var cursorInfoResults: [CursorInfo] = []
150+
if let cursorInfo = CursorInfo(dict, sourcekitd: sourcekitd) {
151+
cursorInfoResults.append(cursorInfo)
116152
}
117-
118-
var location: Location? = nil
119-
if let filepath: String = dict[keys.filepath],
120-
let line: Int = dict[keys.line],
121-
let column: Int = dict[keys.column]
122-
{
123-
let position = Position(
124-
line: line - 1,
125-
// FIXME: we need to convert the utf8/utf16 column, which may require reading the file!
126-
utf16index: column - 1
127-
)
128-
location = Location(uri: DocumentURI(URL(fileURLWithPath: filepath)), range: Range(position))
129-
}
130-
131-
let refactorActionsArray: SKDResponseArray? = dict[keys.refactor_actions]
132-
133-
let receiversArray: SKDResponseArray? = dict[keys.receivers]
134-
let receiverUsrs = receiversArray?.compactMap { $0[keys.usr] as String? } ?? []
135-
136-
return CursorInfo(
137-
SymbolDetails(
138-
name: dict[keys.name],
139-
containerName: nil,
140-
usr: dict[keys.usr],
141-
bestLocalDeclaration: location,
142-
kind: kind.asSymbolKind(self.sourcekitd.values),
143-
isDynamic: dict[keys.isDynamic] ?? false,
144-
receiverUsrs: receiverUsrs
145-
),
146-
annotatedDeclaration: dict[keys.annotated_decl],
147-
documentationXML: dict[keys.doc_full_as_xml],
148-
refactorActions: [SemanticRefactorCommand](
149-
array: refactorActionsArray,
153+
cursorInfoResults += dict[keys.secondarySymbols]?.compactMap { CursorInfo($0, sourcekitd: sourcekitd) } ?? []
154+
let refactorActions =
155+
[SemanticRefactorCommand](
156+
array: dict[keys.refactor_actions],
150157
range: range,
151158
textDocument: TextDocumentIdentifier(uri),
152159
keys,
153160
self.sourcekitd.api
154-
)
155-
)
161+
) ?? []
162+
return (cursorInfoResults, refactorActions)
156163
}
157164
}

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -509,35 +509,45 @@ extension SwiftLanguageServer {
509509
public func hover(_ req: HoverRequest) async throws -> HoverResponse? {
510510
let uri = req.textDocument.uri
511511
let position = req.position
512-
guard let cursorInfo = try await cursorInfo(uri, position..<position) else {
513-
return nil
514-
}
512+
let cursorInfoResults = try await cursorInfo(uri, position..<position).cursorInfo
515513

516-
guard let name: String = cursorInfo.symbolInfo.name else {
517-
// There is a cursor but we don't know how to deal with it.
518-
return nil
519-
}
514+
let symbolDocumentations = cursorInfoResults.compactMap { (cursorInfo) -> String? in
515+
guard let name: String = cursorInfo.symbolInfo.name else {
516+
// There is a cursor but we don't know how to deal with it.
517+
return nil
518+
}
520519

521-
/// Prepend backslash to `*` and `_`, to prevent them
522-
/// from being interpreted as markdown.
523-
func escapeNameMarkdown(_ str: String) -> String {
524-
return String(str.flatMap({ ($0 == "*" || $0 == "_") ? ["\\", $0] : [$0] }))
525-
}
520+
/// Prepend backslash to `*` and `_`, to prevent them
521+
/// from being interpreted as markdown.
522+
func escapeNameMarkdown(_ str: String) -> String {
523+
return String(str.flatMap({ ($0 == "*" || $0 == "_") ? ["\\", $0] : [$0] }))
524+
}
526525

527-
var result = escapeNameMarkdown(name)
528-
if let doc = cursorInfo.documentationXML {
529-
result += """
526+
var result = escapeNameMarkdown(name)
527+
if let doc = cursorInfo.documentationXML {
528+
result += """
530529
531-
\(orLog("Convert XML to Markdown") { try xmlDocumentationToMarkdown(doc) } ?? doc)
532-
"""
533-
} else if let annotated: String = cursorInfo.annotatedDeclaration {
534-
result += """
530+
\(orLog("Convert XML to Markdown") { try xmlDocumentationToMarkdown(doc) } ?? doc)
531+
"""
532+
} else if let annotated: String = cursorInfo.annotatedDeclaration {
533+
result += """
535534
536-
\(orLog("Convert XML to Markdown") { try xmlDocumentationToMarkdown(annotated) } ?? annotated)
537-
"""
535+
\(orLog("Convert XML to Markdown") { try xmlDocumentationToMarkdown(annotated) } ?? annotated)
536+
"""
537+
}
538+
return result
539+
}
540+
541+
if symbolDocumentations.isEmpty {
542+
return nil
538543
}
539544

540-
return HoverResponse(contents: .markupContent(MarkupContent(kind: .markdown, value: result)), range: nil)
545+
let joinedDocumentation = symbolDocumentations.joined(separator: "\n# Alternative result\n")
546+
547+
return HoverResponse(
548+
contents: .markupContent(MarkupContent(kind: .markdown, value: joinedDocumentation)),
549+
range: nil
550+
)
541551
}
542552

543553
public func documentColor(_ req: DocumentColorRequest) async throws -> [ColorInformation] {
@@ -679,13 +689,7 @@ extension SwiftLanguageServer {
679689
additionalParameters: additionalCursorInfoParameters
680690
)
681691

682-
guard let cursorInfoResponse else {
683-
throw ResponseError.unknown("CursorInfo failed.")
684-
}
685-
guard let refactorActions = cursorInfoResponse.refactorActions else {
686-
return []
687-
}
688-
let codeActions: [CodeAction] = refactorActions.compactMap {
692+
return cursorInfoResponse.refactorActions.compactMap {
689693
do {
690694
let lspCommand = try $0.asCommand()
691695
return CodeAction(title: $0.title, kind: .refactor, command: lspCommand)
@@ -694,7 +698,6 @@ extension SwiftLanguageServer {
694698
return nil
695699
}
696700
}
697-
return codeActions
698701
}
699702

700703
func retrieveQuickFixCodeActions(_ params: CodeActionRequest) async throws -> [CodeAction] {

Sources/SourceKitLSP/Swift/SymbolInfo.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ extension SwiftLanguageServer {
7070
let uri = req.textDocument.uri
7171
let snapshot = try documentManager.latestSnapshot(uri)
7272
let position = await self.adjustPositionToStartOfIdentifier(req.position, in: snapshot)
73-
guard let cursorInfo = try await cursorInfo(uri, position..<position) else {
74-
return []
75-
}
76-
return [cursorInfo.symbolInfo]
73+
return try await cursorInfo(uri, position..<position).cursorInfo.map { $0.symbolInfo }
7774
}
7875
}

0 commit comments

Comments
 (0)