Skip to content

Commit 41cae20

Browse files
committed
Add sourcekit/workspace/symbolNames and sourcekit/workspace/symbolInfo
These two new SourceKit-LSP extension requests power "Open Quickly" in editors: the client first calls `sourcekit/workspace/symbolNames` to fetch a sorted, deduplicated list of all symbol names from the index, then calls `sourcekit/workspace/symbolInfo` with a chosen name to retrieve full symbol details (kind, location, container name, USR). For symbols located in `.swiftinterface` / `.swiftmodule` files, the response uses `WorkspaceSymbol` with a deferred location and a `data` payload (containing the USR) so the client can resolve the concrete interface position lazily via the standard `workspaceSymbol/resolve` request. Clients that do not advertise `workspace.symbol.resolveSupport` receive a plain `SymbolInformation` instead. The existing `workspace/symbol` handler is refactored to share the new `workspaceSymbolItem(for:in:copiedFileMap:)` helper. Also adds: - `Array.isSortedAndUnique` / `sortAndDedupe()` (specialised for String) to efficiently deduplicate the symbol name list - `clientSupportsWorkspaceSymbolResolve` capability check - Documentation for Open Quickly and Jump to Definition
1 parent 889f0f2 commit 41cae20

12 files changed

Lines changed: 930 additions & 32 deletions

File tree

Contributor Documentation/LSP Extensions.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,65 @@ export interface IsIndexingResult {
389389
}
390390
```
391391
392+
## `sourcekit/workspace/symbolInfo`
393+
394+
New request that returns structured location information for a list of exact symbol names.
395+
396+
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.
397+
398+
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.
400+
- 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.
401+
402+
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`.
403+
404+
> [!IMPORTANT]
405+
> This request is experimental and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.
406+
407+
- params: `WorkspaceSymbolInfoParams`
408+
- result: `WorkspaceSymbolInfoResult`
409+
410+
```ts
411+
export interface WorkspaceSymbolInfoParams {
412+
/**
413+
* Symbol names to resolve.
414+
*/
415+
names: string[];
416+
}
417+
418+
export interface WorkspaceSymbolInfoResult {
419+
/**
420+
* Flat list of all symbols matching the requested names.
421+
* Each item carries the symbol name in its `name` field.
422+
*/
423+
results: WorkspaceSymbolItem[];
424+
}
425+
```
426+
427+
## `sourcekit/workspace/symbolNames`
428+
429+
New request that returns the flat, deduplicated list of every symbol name in the workspace index, including names from indexed system modules (stdlib, SDK frameworks).
430+
431+
Clients use this list to drive a local search UI (fuzzy matching, prefix filtering, etc.) without a round-trip per keystroke. After the user selects a name, send a `sourcekit/workspace/symbolInfo` request to resolve it to concrete locations.
432+
433+
> [!IMPORTANT]
434+
> This request is experimental and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.
435+
436+
- params: `WorkspaceSymbolNamesParams`
437+
- result: `WorkspaceSymbolNamesResult`
438+
439+
```ts
440+
export interface WorkspaceSymbolNamesParams {}
441+
442+
export interface WorkspaceSymbolNamesResult {
443+
/**
444+
* Flat, deduplicated list of all symbol names in the workspace index,
445+
* including names from system modules (stdlib, SDK).
446+
*/
447+
names: string[];
448+
}
449+
```
450+
392451
## `window/didChangeActiveDocument`
393452

394453
New notification from the client to the server, telling SourceKit-LSP which document is the currently active primary document.
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Jump to Definition
2+
3+
Jump to definition for SDK/stdlib symbols works by generating a
4+
textual Swift interface on demand and returning a `sourcekit-lsp://`
5+
URI that the client can fetch via `workspace/getReferenceDocument`.
6+
7+
## Requests Involved
8+
9+
| Request | Direction | Purpose |
10+
|---|---|---|
11+
| `textDocument/definition` | Client → Server | Resolve the symbol under the cursor to a location |
12+
| `workspace/getReferenceDocument` | Client → Server | Fetch the content of a `sourcekit-lsp://` URI |
13+
14+
`workspace/getReferenceDocument` is a SourceKit-LSP extension. The
15+
client must advertise support in `ClientCapabilities.experimental`:
16+
17+
```json
18+
{ "workspace/getReferenceDocument": { "supported": true } }
19+
```
20+
21+
Without this capability the server writes the interface to a temporary
22+
file and returns a `file://` URI instead.
23+
24+
## Workflow
25+
26+
```
27+
Client Server
28+
│ │
29+
│── textDocument/definition ──────────────▶│
30+
│◀─ Location { │
31+
│ uri: "sourcekit-lsp://...", │
32+
│ range: { line: 42, character: 14 } │
33+
│ } │
34+
│ │
35+
│── workspace/getReferenceDocument ───────▶│
36+
│◀─ { content: "..." } ────────────────────│
37+
│ │
38+
│ [open tab, scroll to range] │
39+
```
40+
41+
1. **Definition** — the client requests the definition of the symbol
42+
at the cursor. For source-defined symbols the server returns a
43+
`file://` URI with the exact source location. For SDK/stdlib
44+
symbols it returns a `sourcekit-lsp://` URI and sets `range` to
45+
the symbol's position within the generated interface (computed
46+
server-side via `editor.find_usr`).
47+
2. **Content retrieval** — the client fetches the generated interface
48+
via `workspace/getReferenceDocument` to display its content. The
49+
client scrolls to `range` from the definition response — `symbolPosition`
50+
is not used here since the position is already known from step 1.
51+
52+
## Server-Side Flow
53+
54+
### 1. `textDocument/definition` handling
55+
56+
The server first attempts an index-based lookup
57+
(`indexBasedDefinition`). For system/SDK symbols the index record
58+
points to a `.swiftinterface` or `.swiftmodule` file, so the handler
59+
calls:
60+
61+
```
62+
definitionInInterface(
63+
moduleName: <from SymbolDetails.systemModule>,
64+
groupName: <from SymbolDetails.systemModule>,
65+
symbolUSR: <symbol.usr>,
66+
originatorUri: <the file the cursor is in>
67+
)
68+
```
69+
70+
### 2. `openGeneratedInterface`
71+
72+
`definitionInInterface` delegates to
73+
`SwiftLanguageService.openGeneratedInterface`, which:
74+
75+
1. Constructs a fully-resolved `GeneratedInterfaceDocumentURLData`
76+
using `init(moduleName:groupName:primaryFile:)`:
77+
- `sourcekitdDocumentName` is synthesised as
78+
`<moduleName>.<groupName>.<hash>` where `hash` is
79+
`abs(buildSettingsFile.stringValue.hashValue)`.
80+
- `buildSettingsFrom` is set to `originatorUri.buildSettingsFile`
81+
— the build settings file of the **requesting source file**, not
82+
the module file. This ensures sourcekitd uses the same compiler
83+
arguments as the file that triggered the request.
84+
2. Calls `generatedInterfaceManager.position(ofUsr:in:)` to find the
85+
symbol's position within the generated interface (see below).
86+
3. Returns `GeneratedInterfaceDetails(uri: sourcekit-lsp://...,
87+
position: <symbol position>)`.
88+
89+
The URI has no USR fragment. The position is returned separately and
90+
used as `Location.range` in the definition response.
91+
92+
### 3. Interface generation and caching
93+
94+
`GeneratedInterfaceManager` opens the interface in sourcekitd via
95+
`editor.open.interface`:
96+
97+
```
98+
keys.name: "<moduleName>.<groupName>.<hash>"
99+
keys.moduleName: "<moduleName>"
100+
keys.groupName: "<groupName>" // if present
101+
keys.synthesizedExtension: 1
102+
keys.compilerArgs: [... compiler arguments from build settings ...]
103+
```
104+
105+
The resulting `sourceText` is cached in memory keyed by
106+
`sourcekitdDocumentName`. Subsequent requests for the same module +
107+
build context reuse the cached snapshot.
108+
109+
### 4. Symbol position within the interface
110+
111+
`GeneratedInterfaceManager.position(ofUsr:in:)` sends
112+
`editor.find_usr` to sourcekitd:
113+
114+
```
115+
keys.sourceFile: "<sourcekitdDocumentName>"
116+
keys.usr: "<symbolUSR>"
117+
```
118+
119+
sourcekitd returns a byte offset, which is converted to a 0-based
120+
`Position` via `DocumentSnapshot.positionOf(utf8Offset:)`.
121+
122+
### 5. URI returned to the client
123+
124+
The `sourcekit-lsp://` URI is fully resolved — `sourcekitdDocument`
125+
is always present, and there is no USR fragment:
126+
127+
```
128+
sourcekit-lsp://generated-swift-interface/Swift.String.swiftinterface
129+
?moduleName=Swift
130+
&groupName=String
131+
&sourcekitdDocument=Swift.String.12345678
132+
&buildSettingsFrom=file:///path/to/main.swift
133+
```
134+
135+
The `range` in the returned `Location` carries the symbol's position
136+
in the interface, so the client knows where to scroll without calling
137+
`workspace/getReferenceDocument` first.
138+
139+
### 6. `workspace/getReferenceDocument` handling
140+
141+
Because the URI is fully resolved (`sourcekitdDocumentName != nil`)
142+
the server skips the stub-resolution path and goes straight to the
143+
language service:
144+
145+
```swift
146+
primaryLanguageService(for: buildSettingsUri, ...).getReferenceDocument(req)
147+
```
148+
149+
`SwiftLanguageService.getReferenceDocument` retrieves the cached
150+
interface snapshot. Since the URI carries no USR fragment,
151+
`symbolPosition` in the response is `nil` — the client uses
152+
`Location.range` from the definition response instead.

0 commit comments

Comments
 (0)