Skip to content

Commit 145c2fa

Browse files
committed
Review
1 parent 41cae20 commit 145c2fa

7 files changed

Lines changed: 64 additions & 34 deletions

File tree

Contributor Documentation/LSP Extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ New request that returns structured location information for a list of exact sym
396396
Unlike the standard `workspace/symbol` request (which accepts a fuzzy query string), this request takes exact names — typically obtained from `sourcekit/workspace/symbolNames` — and returns all index occurrences for each name across every workspace.
397397
398398
For each name the response contains zero or more `WorkspaceSymbolItem` values:
399-
- Source-file symbols carry a `SymbolInformation` with a `file://` URI and the exact 0-based line/column.
399+
- Source-file symbols carry a `SymbolInformation` with a `file://` URI and the exact position.
400400
- SDK/stdlib symbols carry a `WorkspaceSymbol` with `location: .uri(...)` pointing at the `file://` URI of the `.swiftinterface` or `.swiftmodule` file, with the fully-qualified module name as a `?module=` query parameter. The symbol's USR is stored in `data["usr"]`. Call `workspaceSymbol/resolve` to obtain the exact `Location` within the generated interface. The client must advertise `workspace.symbol.resolveSupport`; without it, the raw `file://` URI is returned as `SymbolInformation` instead.
401401

402402
Every requested name is present in the response as a flat array; items carry their name in the `name` field of the `SymbolInformation` or `WorkspaceSymbol`.

Documentation/Open Quickly.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Given a list of names selected by the client after searching, returns structured
2121

2222
The shape of each result item depends on the symbol's origin:
2323

24-
**Source-file symbols** — returned as `SymbolInformation` with a `file://` URI and the exact 0-based line/column from the index.
24+
**Source-file symbols** — returned as `SymbolInformation` with a `file://` URI and the range from the index.
2525

2626
```
2727
→ WorkspaceSymbolInfoRequest { names: ["MyViewController"] }
@@ -128,7 +128,7 @@ Client Server
128128

129129
1. **Discovery** — fetch all names; client filters locally.
130130
2. **Resolution** — send matching name(s) to populate the search result list; server returns symbol details (kind, container name, location) for display.
131-
- Source symbols: `SymbolInformation` with a `file://` URI and exact 0-based line/column. No further steps required.
131+
- Source symbols: `SymbolInformation` with a `file://` URI and exact position. No further steps required.
132132
- SDK/stdlib symbols: `WorkspaceSymbol` with `location: .uri(file:// URL?module=...)` pointing to the module file and the USR in `data["usr"]`, when the client advertises `workspace.symbol.resolveSupport`. Otherwise falls back to `SymbolInformation` with the raw `file://` URI.
133133
3. **Location resolution** — call `workspaceSymbol/resolve` with the selected `WorkspaceSymbol` to open the generated interface and resolve the symbol position. The server synthesizes the final `sourcekit-lsp://` URI and fills in `location.range`.
134134
4. **Content retrieval** — fetch the generated interface text. The editor scrolls to `location.range.start` from the resolve step.

Sources/SourceKitLSP/CapabilityRegistry.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ package final actor CapabilityRegistry {
119119
}
120120

121121
/// Whether the client supports `workspaceSymbol/resolve` for lazy location resolution.
122-
/// Requires `workspace.symbol.resolveSupport.properties` to contain `"location"` or `"location.range"`.
122+
/// Requires `workspace.symbol.resolveSupport.properties` to contain `"location"` or `"location."`.
123123
package nonisolated var clientSupportsWorkspaceSymbolResolve: Bool {
124124
return clientCapabilities.workspace?.symbol?.resolveSupport?.properties.contains(where: {
125125
$0 == "location" || $0.hasPrefix("location.")

Sources/SourceKitLSP/MessageHandlingDependencyTracker.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,10 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
242242
self = .freestanding
243243
case is WorkspaceSemanticTokensRefreshRequest:
244244
self = .freestanding
245-
case is WorkspaceSymbolResolveRequest:
245+
case is WorkspaceSymbolInfoRequest:
246246
self = .freestanding
247247
case is WorkspaceSymbolNamesRequest:
248248
self = .freestanding
249-
case is WorkspaceSymbolInfoRequest:
250-
self = .freestanding
251249
case is WorkspaceSymbolResolveRequest:
252250
self = .freestanding
253251
case is WorkspaceSymbolsRequest:

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,9 +1773,13 @@ extension SourceKitLSPServer {
17731773

17741774
/// Handle a workspace/symbolNames request, returning the name list.
17751775
func workspaceSymbolNames(_ req: WorkspaceSymbolNamesRequest) async throws -> WorkspaceSymbolNamesResponse {
1776-
var symbols = try await self.workspaces.asyncFlatMap { workspace in
1777-
try await workspace.uncheckedIndex?.allSymbolNames() ?? []
1778-
}
1776+
var symbols = await self.workspaces
1777+
.concurrentMap { workspace in
1778+
await orLog("Getting symbol names in workspace") {
1779+
try await workspace.uncheckedIndex?.allSymbolNames() ?? []
1780+
} ?? []
1781+
}
1782+
.flatMap { $0 }
17791783
if !symbols.isSortedAndUnique {
17801784
symbols.sortAndDedupe()
17811785
}
@@ -1784,10 +1788,11 @@ extension SourceKitLSPServer {
17841788

17851789
/// Map a `SymbolOccurrence` from the index to a `WorkspaceSymbolItem` suitable for returning in a
17861790
/// `workspace/symbolInfo` response.
1787-
private func workspaceSymbolItem(
1791+
private nonisolated func workspaceSymbolItem(
17881792
for symbolOccurrence: SymbolOccurrence,
17891793
in index: CheckedIndex,
1790-
copiedFileMap: CopiedFileMap
1794+
copiedFileMap: CopiedFileMap,
1795+
canUseWorkspaceSymbolResolve: Bool
17911796
) throws -> WorkspaceSymbolItem? {
17921797
let containerNames = try index.containerNames(of: symbolOccurrence)
17931798
let containerName: String? =
@@ -1803,7 +1808,7 @@ extension SourceKitLSPServer {
18031808
// For SDK symbols (location in `.swiftinterface`/`.swiftmodule`), return a `WorkspaceSymbol`
18041809
// with a deferred location so the client can resolve it via `workspaceSymbol/resolve`.
18051810
// Falls through to `SymbolInformation` with regular file:// URL for clients without `workspace.symbol.resolveSupport`.
1806-
if capabilityRegistry?.clientSupportsWorkspaceSymbolResolve ?? false,
1811+
if canUseWorkspaceSymbolResolve,
18071812
symbolOccurrence.location.path.hasSuffix(".swiftinterface")
18081813
|| symbolOccurrence.location.path.hasSuffix(".swiftmodule")
18091814
{
@@ -1865,25 +1870,46 @@ extension SourceKitLSPServer {
18651870
/// Every requested name is present as a key in the response, mapping to an empty array when there
18661871
/// are no occurrences.
18671872
func workspaceSymbolInfo(_ req: WorkspaceSymbolInfoRequest) async throws -> WorkspaceSymbolInfoResponse {
1868-
var result: [WorkspaceSymbolItem] = []
1869-
for workspace in workspaces {
1873+
let canUseWorkspaceSymbolResolve = self.capabilityRegistry?.clientSupportsWorkspaceSymbolResolve ?? false
1874+
1875+
var groupedResultPerWorkspace = await workspaces.concurrentMap { workspace -> [String: [WorkspaceSymbolItem]] in
18701876
guard let index = await workspace.index(checkedFor: .deletedFiles) else {
1871-
continue
1877+
return [:]
18721878
}
1879+
var result: [String: [WorkspaceSymbolItem]] = [:]
18731880
let copiedFileMap = await workspace.buildServerManager.cachedCopiedFileMap
18741881
for name in req.names {
1882+
if Task.isCancelled { return [:] }
18751883
var symbols: [SymbolOccurrence] = []
1876-
try index.forEachCanonicalSymbolOccurrence(byName: name) { symbolOccurrence in
1877-
symbols.append(symbolOccurrence)
1878-
return true
1884+
_ = orLog("getting symbol information") {
1885+
try index.forEachCanonicalSymbolOccurrence(byName: name) { symbolOccurrence in
1886+
symbols.append(symbolOccurrence)
1887+
return true
1888+
}
18791889
}
1880-
try Task.checkCancellation()
1881-
for symbol in symbols {
1882-
if let item = try self.workspaceSymbolItem(for: symbol, in: index, copiedFileMap: copiedFileMap) {
1883-
result.append(item)
1890+
if Task.isCancelled { return [:] }
1891+
result[name] = symbols.compactMap { symbol in
1892+
orLog("getting symbol information") {
1893+
try self.workspaceSymbolItem(
1894+
for: symbol,
1895+
in: index,
1896+
copiedFileMap: copiedFileMap,
1897+
canUseWorkspaceSymbolResolve: canUseWorkspaceSymbolResolve
1898+
)
18841899
}
18851900
}
18861901
}
1902+
return result
1903+
}
1904+
1905+
// Flatten the result.
1906+
var result: [WorkspaceSymbolItem] = []
1907+
for name in req.names {
1908+
for grouped in groupedResultPerWorkspace {
1909+
if let items = grouped[name] {
1910+
result.append(contentsOf: items)
1911+
}
1912+
}
18871913
}
18881914
return WorkspaceSymbolInfoResponse(results: result)
18891915
}
@@ -1899,7 +1925,7 @@ extension SourceKitLSPServer {
18991925
guard
19001926
case .uri(let uriOnly) = symbol.location,
19011927
let urlComponents = URLComponents(url: uriOnly.uri.arbitrarySchemeURL, resolvingAgainstBaseURL: false),
1902-
let fullModuleName = urlComponents.queryItems?.first(where: { $0.name == "module" })?.value
1928+
let fullModuleName = urlComponents.queryItems?.last(where: { $0.name == "module" })?.value
19031929
else {
19041930
return symbol
19051931
}
@@ -1921,7 +1947,14 @@ extension SourceKitLSPServer {
19211947
nil
19221948
}
19231949

1924-
let moduleFileURI = DocumentURI(filePath: urlComponents.path, isDirectory: false)
1950+
let moduleFileURI = DocumentURI(
1951+
{
1952+
var components = urlComponents
1953+
components.fragment = nil
1954+
components.query = nil
1955+
return components.url!
1956+
}()
1957+
)
19251958
for workspace in workspaces {
19261959
let mainFile = await workspace.buildServerManager
19271960
.mainFiles(containing: moduleFileURI)
@@ -1962,6 +1995,7 @@ extension SourceKitLSPServer {
19621995
guard req.query.count >= minWorkspaceSymbolPatternLength else {
19631996
return []
19641997
}
1998+
let canUseWorkspaceSymbolResolve = self.capabilityRegistry?.clientSupportsWorkspaceSymbolResolve ?? false
19651999
var items: [WorkspaceSymbolItem] = []
19662000
for workspace in workspaces {
19672001
guard let index = await workspace.index(checkedFor: .deletedFiles) else {
@@ -1987,7 +2021,12 @@ extension SourceKitLSPServer {
19872021
}
19882022
try Task.checkCancellation()
19892023
items += try symbols.sorted(by: <).compactMap {
1990-
try self.workspaceSymbolItem(for: $0, in: index, copiedFileMap: copiedFileMap)
2024+
try self.workspaceSymbolItem(
2025+
for: $0,
2026+
in: index,
2027+
copiedFileMap: copiedFileMap,
2028+
canUseWorkspaceSymbolResolve: canUseWorkspaceSymbolResolve
2029+
)
19912030
}
19922031
}
19932032

Sources/SwiftExtensions/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
set(sources
22
Array+Safe.swift
3+
Array+SortAndDedupe.swift
34
AsyncUtils.swift
45
Cache.swift
56
CartesianProduct.swift

Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,6 @@ final class WorkspaceSymbolInfoTests: XCTestCase {
9393
XCTAssertFalse(moduleParam.isEmpty, "?module= query parameter should be non-empty")
9494
XCTAssertNil(urlComponents.fragment, "URI should not contain a fragment")
9595

96-
guard case .dictionary(let dataDict) = symbol.data,
97-
case .string(let usr) = dataDict["usr"]
98-
else {
99-
XCTFail("Expected data[\"usr\"] string, got \(String(describing: symbol.data))")
100-
return
101-
}
102-
XCTAssertFalse(usr.isEmpty, "Expected non-empty USR in data[\"usr\"]")
103-
10496
// workspaceSymbol/resolve turns the deferred URI into a sourcekit-lsp:// location with a range.
10597
let resolved = try await project.testClient.send(
10698
WorkspaceSymbolResolveRequest(workspaceSymbol: symbol)

0 commit comments

Comments
 (0)