-
Notifications
You must be signed in to change notification settings - Fork 375
Add workspace/symbolNames and workspace/symbolInfo LSP extensions
#2619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dbb1ec7
b5a5fb2
29251d1
d208786
496c1cf
9769c2a
f9f5830
f3a677d
3dca024
139f6c7
023a55e
9287f67
5bc9f3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -389,6 +389,65 @@ export interface IsIndexingResult { | |
| } | ||
| ``` | ||
|
|
||
| ## `sourcekit/workspace/symbolInfo` | ||
|
|
||
| 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. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would argue that the If we want to go the URI route: Could we just say that the returned URI may contain a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
|
|
||
| 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. | ||
|
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. | ||
|
|
||
| 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. |
Uh oh!
There was an error while loading. Please reload this page.