|
| 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. |
| 153 | + |
| 154 | +## Comparison with Open Quickly |
| 155 | + |
| 156 | +| Aspect | Jump to Definition | Open Quickly | |
| 157 | +|---|---|---| |
| 158 | +| URI type | Fully resolved (`sourcekitdDocumentName` set) | Stub (`sourcekitdDocumentName` nil) | |
| 159 | +| USR in URI | No fragment | Fragment (`#<USR>`) | |
| 160 | +| `buildSettingsFrom` | Build settings file of the **requesting source file** | Path of the `.swiftinterface`/`.swiftmodule` from the index record | |
| 161 | +| Symbol position delivery | `Location.range` in the definition response | `symbolPosition` in the `getReferenceDocument` response | |
| 162 | +| Stub resolution in `getReferenceDocument` | Skipped | `resolvedReferenceDocumentRequest` looks up `mainFiles(containing:)` | |
| 163 | +| Fallback without capability | Interface written to a temp `file://` | Raw `file://` path from index record | |
0 commit comments