Skip to content

Commit 1e72500

Browse files
authored
Merge pull request #1017 from ahoppen/ahoppen/secondary-cursor-info-results
Report secondary cursor info results
2 parents 7f59f6f + 7572fb9 commit 1e72500

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
@@ -1709,49 +1709,24 @@ extension SourceKitServer {
17091709
return try await languageService.declaration(req)
17101710
}
17111711

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

1754-
guard let index = await self.workspaceForDocument(uri: req.textDocument.uri)?.index else {
1729+
guard let index = await self.workspaceForDocument(uri: uri)?.index else {
17551730
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
17561731
return [bestLocalDeclaration]
17571732
} else {
@@ -1800,16 +1775,46 @@ extension SourceKitServer {
18001775
if URL(fileURLWithPath: occurrence.location.path).pathExtension == "swiftinterface" {
18011776
// If the location is in `.swiftinterface` file, use moduleName to return textual interface.
18021777
return try await self.definitionInInterface(
1803-
req,
18041778
moduleName: occurrence.location.moduleName,
18051779
symbolUSR: occurrence.symbol.usr,
1780+
originatorUri: uri,
18061781
languageService: languageService
18071782
)
18081783
}
18091784
return indexToLSPLocation(occurrence.location)
18101785
}
18111786
}
18121787

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

1838+
/// Generate the generated interface for the given module, write it to disk and return the location to which to jump
1839+
/// to get to the definition of `symbolUSR`.
1840+
///
1841+
/// `originatorUri` is the URI of the file from which the definition request is performed. It is used to determine the
1842+
/// compiler arguments to generate the generated inteface.
18331843
func definitionInInterface(
1834-
_ req: DefinitionRequest,
18351844
moduleName: String,
18361845
symbolUSR: String?,
1846+
originatorUri: DocumentURI,
18371847
languageService: ToolchainLanguageServer
18381848
) async throws -> Location {
1839-
let openInterface = OpenInterfaceRequest(textDocument: req.textDocument, name: moduleName, symbolUSR: symbolUSR)
1849+
let openInterface = OpenInterfaceRequest(
1850+
textDocument: TextDocumentIdentifier(originatorUri),
1851+
name: moduleName,
1852+
symbolUSR: symbolUSR
1853+
)
18401854
guard let interfaceDetails = try await languageService.openInterface(openInterface) else {
18411855
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
18421856
}

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 {
@@ -105,48 +141,19 @@ extension SwiftLanguageServer {
105141

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

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

Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift

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

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

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

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

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

542552
public func documentColor(_ req: DocumentColorRequest) async throws -> [ColorInformation] {
@@ -668,13 +678,7 @@ extension SwiftLanguageServer {
668678
additionalParameters: additionalCursorInfoParameters
669679
)
670680

671-
guard let cursorInfoResponse else {
672-
throw ResponseError.unknown("CursorInfo failed.")
673-
}
674-
guard let refactorActions = cursorInfoResponse.refactorActions else {
675-
return []
676-
}
677-
let codeActions: [CodeAction] = refactorActions.compactMap {
681+
return cursorInfoResponse.refactorActions.compactMap {
678682
do {
679683
let lspCommand = try $0.asCommand()
680684
return CodeAction(title: $0.title, kind: .refactor, command: lspCommand)
@@ -683,7 +687,6 @@ extension SwiftLanguageServer {
683687
return nil
684688
}
685689
}
686-
return codeActions
687690
}
688691

689692
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)