Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
176 changes: 176 additions & 0 deletions Documentation/Jump to Definition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Jump to Definition

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

Let’s highlight that this step only happens for sourcekit-lsp:// URLs.

Copy link
Copy Markdown
Member Author

@rintaro rintaro May 12, 2026

Choose a reason for hiding this comment

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

This document is for jump to definition to SDK symbols that has sourcekit-lsp:// scheme. I'm renaming the doc.

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

The client doesn’t necessarily need to set the version to 1. It just needs to be monotonically increasing as the file is modified (which it isn’t in this case). I’d just remove the mention of version here.

Copy link
Copy Markdown
Member Author

@rintaro rintaro May 12, 2026

Choose a reason for hiding this comment

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

I'm keeping this as-is. This is a guidance for client implementation, they need to specify some value.

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

General feedback on this document: It breaks lines very aggressively. Since the rest of the code base allows 120 columns, I think it would be good to also use 120 columns here or just not have a column limit for Markdown files at all. Makes it more readable in its raw format.


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

The URI has no USR fragment. The position is returned separately and
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 don’t think that we have any URIs with USR fragments anymore, right? So this is a moot point.

Same below.

used as `Location.range` in the definition response.

### 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

The `sourcekit-lsp://` URI is fully resolved — `sourcekitdDocument`
is always present, and there is no USR fragment:

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

The `range` in the returned `Location` carries the symbol's position
in the interface, so the client knows where to scroll without calling
`workspace/getReferenceDocument` first.
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.

This is also outdated.


### 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