Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions Contributor Documentation/LSP Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,65 @@ export interface IsIndexingResult {
}
```

## `sourcekit/workspace/symbolInfo`
Comment thread
ahoppen marked this conversation as resolved.

New request that returns structured location information for a list of exact symbol names.

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.

For each name the response contains zero or more `WorkspaceSymbolItem` values:
- Source-file symbols carry a `SymbolInformation` with a `file://` URI and the exact position.
- 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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to make the structure of the URI part of the API contract? I would prefer to keep the URI opaque and just define that clients must call workspaceSymbol/resolve to resolve the location. This gives us more flexibility going forward.

Copy link
Copy Markdown
Member Author

@rintaro rintaro Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some clients want to show module/group names in the candidate list. So we need to include that information somehow in the response in a way clients can read. Yes it's an API contract. We have seveal options:

  • Use .data - in general data should be opaque to client.
  • Add dedicated (extension) field (e.g. module) to WorkspaceSymbol - I want to avoid this at all costs.
  • Include module names to WorkspaceSymbol.containerName - currently we're using this field to show nesting types, and this field is meant to be displayed as-is in the client. I didn't come up with nice format for including module names.
  • Use URL - I think this is the least bad option

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that the data is more structured than the URI and I’d prefer that. Also, I personally don’t see a big issue with extending WorkspaceSymbol and would have probably done that.

If we want to go the URI route: Could we just say that the returned URI may contain a module query parameter and keep the rest of the URI opaque?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path is actually a contract. Clients may look into the file path and derive the characteristics of the SDK E.g. "macOS 26.0 SDK". The way of the presentation is up-to the client so we don't want to return the exact text to show.

Comment on lines +399 to +400
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, I would like to not make the type of URLs that are returned for specific symbols part of the API contract. Instead, we should a general sentence like:

URLs may contain a module query parameter that informs the client of the name that is represented by the file.


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`.

> [!IMPORTANT]
> This request is experimental and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.
Comment thread
ahoppen marked this conversation as resolved.

- params: `WorkspaceSymbolInfoParams`
- result: `WorkspaceSymbolInfoResult`

```ts
export interface WorkspaceSymbolInfoParams {
/**
* Symbol names to resolve.
*/
names: string[];
}

export interface WorkspaceSymbolInfoResult {
/**
* Flat list of all symbols matching the requested names.
* Each item carries the symbol name in its `name` field.
*/
results: WorkspaceSymbolItem[];
}
```

## `sourcekit/workspace/symbolNames`

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

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.

> [!IMPORTANT]
> This request is experimental and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.

- params: `WorkspaceSymbolNamesParams`
- result: `WorkspaceSymbolNamesResult`

```ts
export interface WorkspaceSymbolNamesParams {}

export interface WorkspaceSymbolNamesResult {
/**
* Flat, deduplicated list of all symbol names in the workspace index,
* including names from system modules (stdlib, SDK).
*/
names: string[];
}
```

## `window/didChangeActiveDocument`

New notification from the client to the server, telling SourceKit-LSP which document is the currently active primary document.
Expand Down
146 changes: 146 additions & 0 deletions Documentation/Jump to Definition for SDK Symbols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Jump to Definition for SDK Symbols

Jump to definition for SDK/stdlib symbols works by generating a textual Swift interface on demand and returning a
`sourcekit-lsp://` URI that the client can fetch via `workspace/getReferenceDocument`.

`sourcekit-lsp://` URIs are opaque. Clients must treat them as identifiers and must not parse their structure to
extract information.

## Requests Involved

| Request | Direction | Purpose |
|---|---|---|
| `textDocument/definition` | Client → Server | Resolve the symbol under the cursor to a location |
| `workspace/getReferenceDocument` | Client → Server | Fetch the content of a `sourcekit-lsp://` URI |
| `textDocument/didOpen` | Client → Server | Notify the server that the generated interface is open |
| `textDocument/didClose` | Client → Server | Notify the server that the generated interface was closed |

`workspace/getReferenceDocument` is a SourceKit-LSP extension. The client must advertise support in
`ClientCapabilities.experimental`:

```json
{ "workspace/getReferenceDocument": { "supported": true } }
```

Without this capability the server writes the interface to a temporary file and returns a `file://` URI instead.

## Workflow

```
Client Server
│ │
│── textDocument/definition ──────────────▶│
│◀─ Location { │
│ uri: "sourcekit-lsp://...", │
│ range: { line: 42, character: 14 } │
│ } │
│ │
│── workspace/getReferenceDocument ───────▶│
│◀─ { content: "..." } ────────────────────│
│ │
│ [open tab, scroll to range] │
│ │
│── textDocument/didOpen { │
│ uri: "sourcekit-lsp://...", │
│ languageId: "swift", │
│ version: 1, │
│ text: "<interface content>" │ [increment ref count]
│ } ────────────────────────────────────▶│
│ │
│ [user closes the tab] │
│ │
│── textDocument/didClose ────────────────▶│ [decrement ref count;
│ │ close in sourcekitd
│ │ when it reaches 0]
```

1. **Definition** — the client requests the definition of the symbol at the cursor. For source-defined symbols the
server returns a `file://` URI with the exact source location. For SDK/stdlib symbols it returns a
`sourcekit-lsp://` URI and sets `range` to the symbol's position within the generated interface (computed
server-side via `editor.find_usr`).
2. **Content retrieval** — the client fetches the generated interface via `workspace/getReferenceDocument` to display
its content. The client scrolls to `range` from the definition response.
3. **Open notification** — once the client opens the tab it sends `textDocument/didOpen` with the interface content
(already fetched in step 2) as `text`, `languageId` set to `"swift"`, and `version` set to `1`. The server
increments the ref count in `GeneratedInterfaceManager`, keeping the interface open in sourcekitd as long as the
tab is open.
4. **Close notification** — when the client closes the tab it sends `textDocument/didClose`, which decrements the ref
count. When the ref count reaches zero the interface is eligible for eviction from the cache and will eventually be
closed in sourcekitd.

## Server-Side Flow

### 1. `textDocument/definition` handling

The server first attempts an index-based lookup (`indexBasedDefinition`). For system/SDK symbols the index record
points to a `.swiftinterface` or `.swiftmodule` file, so the handler calls:

```
definitionInInterface(
moduleName: <from SymbolDetails.systemModule>,
groupName: <from SymbolDetails.systemModule>,
symbolUSR: <symbol.usr>,
originatorUri: <the file the cursor is in>
)
```

### 2. `openGeneratedInterface`

`definitionInInterface` delegates to `SwiftLanguageService.openGeneratedInterface`, which:

1. Constructs a fully-resolved `GeneratedInterfaceDocumentURLData` using `init(moduleName:groupName:primaryFile:)`:
- `sourcekitdDocumentName` is synthesised as `<moduleName>.<groupName>.<hash>` where `hash` is
`abs(buildSettingsFile.stringValue.hashValue)`.
- `buildSettingsFrom` is set to `originatorUri.buildSettingsFile` — the build settings file of the **requesting
source file**, not the module file. This ensures sourcekitd uses the same compiler arguments as the file that
triggered the request.
2. Calls `generatedInterfaceManager.position(ofUsr:in:)` to find the symbol's position within the generated interface
(see below).
3. Returns `GeneratedInterfaceDetails(uri: sourcekit-lsp://..., position: <symbol position>)`.

### 3. Interface generation and caching

`GeneratedInterfaceManager` opens the interface in sourcekitd via `editor.open.interface`:

```
keys.name: "<moduleName>.<groupName>.<hash>"
keys.moduleName: "<moduleName>"
keys.groupName: "<groupName>" // if present
keys.synthesizedExtension: 1
keys.compilerArgs: [... compiler arguments from build settings ...]
```

The resulting `sourceText` is cached in memory keyed by `sourcekitdDocumentName`. Subsequent requests for the same
module + build context reuse the cached snapshot.

### 4. Symbol position within the interface

`GeneratedInterfaceManager.position(ofUsr:in:)` sends `editor.find_usr` to sourcekitd:

```
keys.sourceFile: "<sourcekitdDocumentName>"
keys.usr: "<symbolUSR>"
```

sourcekitd returns a byte offset, which is converted to a 0-based `Position` via
`DocumentSnapshot.positionOf(utf8Offset:)`.

### 5. URI returned to the client

```
sourcekit-lsp://generated-swift-interface/Swift.String.swiftinterface
?moduleName=Swift
&groupName=String
&sourcekitdDocument=Swift.String.12345678
&buildSettingsFrom=file:///path/to/main.swift
```

### 6. `workspace/getReferenceDocument` handling

The server extracts `buildSettingsFrom` from the URI to determine the language service:

```swift
primaryLanguageService(for: buildSettingsUri, ...).getReferenceDocument(req)
```

`SwiftLanguageService.getReferenceDocument` retrieves the cached interface snapshot.
Loading