Rename all SourceKit-LSP–specific LSP and BSP extension requests and
notifications to use the `sourcekit/` prefix (e.g. `workspace/tests`
→ `sourcekit/workspace/tests`).
* Register the legacy name alias in `MessageRegistry` so incoming
messages from existing clients are still dispatched correctly.
* Advertise both the new and legacy method names in the `experimental`
capability dict so old clients can still discover the capabilities
they know.
* Accept legacy names advertised by old clients in `CapabilityRegistry`.
* Use `LegacyNameFallbackConnection` to retry requests with the legacy
method name when the peer returns `methodNotFound` for a
`sourcekit/`-prefixed request. Used in:
* `SourceKitLSPServer.client` (server→client) to keep old editors working
* `ExternalBuildServerAdapter.connectionToBuildServer` (SourceKit-LSP→BSP)
to keep old build servers working
ThreadSafeBox is a `class` wrapping a `Mutex<Value>`. When stored as
a property of another class or actor, the outer reference type already
provides identity, so the extra ThreadSafeBox class indirection is just a
redundant heap allocation. Replace `ThreadSafeBox<T>` with `Mutex<T>` at
those sites.
Function-local ThreadSafeBox uses are unchanged since they need class
identity to be capturable in `@Sendable` closures.
CustomBuildServerTestProject.buildServerBox is also unchanged: it is
value-captured into a closure stored before `super.init`, which a
~Copyable Mutex can't support without a class wrapper.
Port SourceKitD's fileprivate `computeIfNil` extension from ThreadSafeBox
to Mutex (now `borrowing`).
Delete `Collection+Only.swift`, `Duration+Seconds.swift`,
`FileManagerExtensions.swift`, `PipeAsStringHandler.swift`, and
`URLExtensions.swift` from `Sources/SwiftExtensions/`. The same
APIs now live in swift-tools-protocols as `@_spi(SourceKitLSP)
public` declarations.
Update each call site to add `@_spi(SourceKitLSP) import
ToolsProtocolsSwiftExtensions` next to the existing
`import SwiftExtensions`. Add the
`_ToolsProtocolsSwiftExtensionsForPlugin` dependency and the
`ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin`
module alias to the `SwiftSourceKitClientPlugin` target in both
`Package.swift` and the corresponding `CMakeLists.txt`.
Inner closures captured `sourceKitLSPServer` implicitly. The outer
`Task` already captures it weakly, making it a `var` in scope; inner
@Sendable closures that capture this `var` implicitly trigger the
`SendableClosureCaptures` diagnostic on compiler configurations
without `ImmutableWeakCaptures`.
Add explicit `[weak sourceKitLSPServer]` to each inner closure so the
capture binds cleanly to a fresh weak local.
Delete sourcekit-lsp's `Sources/SwiftExtensions/ThreadSafeBox.swift`
and the now-unused `NSLock+WithLock.swift`.
Use the `Mutex`-backed `ThreadSafeBox` from swift-tools-protocols'
Adjust call sites for the new API: e.g.
`foo.value.mutate()` with `foo.withLock { $0.mutate() }` because it
doesn't provide `_modify` accessor.
Rename languageServices(for:), primaryLanguageService(for:), and their
internal counterparts to use the `forOpenDocument` label, so the
precondition that the document must already be open is visible at call
sites.
Also make primaryLanguageService(forOpenDocument:) throw instead of
returning an optional, and switch several resolve-style handlers from
the find-or-create primaryLanguageService(for:_:) to
primaryLanguageService(forOpenDocument:), since those call sites already
have an open document in hand.
Replace 'weak var' with 'weak let' for weak reference properties that are
set in init and never reassigned, using the Swift 6.3 feature introduced
by SE-0481.
Previously, language services were held in a global registry on
SourceKitLSPServer and shared across workspaces, requiring complex
lifetime tracking (isImmortal, shutdownOrphanedLanguageServices) to
decide when to tear them down. In practice, every language service
already stored workspace-specific properties (buildServerManager,
semanticIndexManagerTask), so sharing them across workspaces was never
truly safe. Giving each Workspace its own service instances simplifies
lifetime management: services are created when needed and shut down
with their workspace.
Remove LanguageService.isImmortal, the workspace parameter from
canHandle(toolchain:), and the initialize/clientInitialized protocol
requirements.
The buildSettingsFile key is an implementation detail of Workspace's
language-service dictionary, so the guard that prevents removing a
still-needed service belongs there rather than in SourceKitLSPServer.
Closing a generated interface document (sourcekit-lsp:// URI) was
removing the language service for the originating source file because
both share the same buildSettingsFile key. Guard the removal so it
only happens when no other open document shares that key.
Relates to #2209
- **`sourcekit/workspace/symbolNames`** — returns a flat, deduplicated
list of every symbol name in the workspace index (source and indexed
system modules). Clients use this to drive their search UI locally.
- **`sourcekit/workspace/symbolInfo`** — given a list of exact symbol
names, returns `WorkspaceSymbolItem` for each occurrence across all
workspaces, for display in the search result list. Source-file symbols
get `SymbolInformation` with a `file://` location. SDK/stdlib symbols
get a `WorkspaceSymbol` with `location: .uri(…)` The client must call
`workspaceSymbol/resolve` after the user selects an SDK/stdlib symbol to
obtain the concrete interface location.
- **`workspaceSymbol/resolve`** — resolves the deferred
`WorkspaceSymbol` location from `sourcekit/workspace/symbolInfo`. Parses
the `?module=` value into `moduleName`/`groupName`, finds a real source
file via `mainFiles(containing:)`, calls `openGeneratedInterface`, and
returns the symbol with `location` replaced by a full
`sourcekit-lsp://generated-swift-interface/` URI + range (or a temp
`file://` path for clients without `workspace/getReferenceDocument`
support).
- WorkspaceEdit.adjusted(for:) was using originalURI(for:) ?? uri, which
skips the file-existence check; switch to adjustedURI(for:)
- TypeHierarchyItem.adjusted(for:) was not adjusting the URI stored in
data, unlike CallHierarchyItem
`CopiedFileMap` previously held methods like
`workspaceEditAdjustedForCopiedFiles`,
`callHierarchyItemAdjustedForCopiedFiles`, etc. that encoded knowledge
of high-level LSP message types into `BuildServerIntegration`.
Replace them with `adjusted(for:)` methods on each LSP type
(`Location`, `[Location]`, `LocationsOrLocationLinksResponse`,
`WorkspaceEdit`, `CallHierarchyItem`, `TypeHierarchyItem`) in a new
`LSP+CopiedFileMap.swift` in the SourceKitLSP module. The primitive
URI remapping is extracted into `CopiedFileMap.adjustedURI(for:)`
which remains in `BuildServerIntegration`.
`CallHierarchyItem.adjusted(for:)` also drops manual `LSPAny`
dictionary construction in favour of `HierarchyItemData`.
Introduce `SourceKitDCore` as the protocol boundary between dylib
lifecycle management and the high-level `SourceKitD` API. Its single
lifecycle entry point, `initializeService(api:notificationCallback:)`,
receives the already-loaded `sourcekitd_api_functions_t` from
`SourceKitD.init(core:)`.
`SourceKitDCoreImpl` is the standard implementation: `init` opens the
dylib; `initializeService` registers any plugin paths, calls
`api.initialize()`, and wires the notification handler; `deinit` calls
`shutdown()` and closes the handle. Pre-initialized conformances
implement `initializeService` as a no-op.
Wire a `sourcekitdCoreInjector` hook through `Hooks` so an embedding
host can return a pre-initialized `SourceKitDCore` for a given toolchain
path, preventing `sourcekitd_initialize()` from being called a second
time.
Declare `SourceKitDCoreForPlugin` at its use sites so each call site
can express the exact deinit behavior it needs: `dlclose` for handles
acquired via `RTLD_NOLOAD`, and `leak` for externally-owned handles.
Extract the copied-file adjustment logic from BuildServerManager into a
Sendable value type CopiedFileMap. Call sites snapshot cachedCopiedFileMap
once per request (hoisted before loops) instead of awaiting the actor on
every iteration.
Add computed properties to `SymbolLocation` that centralise the
index-to-LSP coordinate conversion:
- `uri: DocumentURI?` — returns nil when `path` is empty.
- `lspPosition: Position` — converts the 1-based line/utf8Column to a
0-based LSP Position, using UTF-8 column as a UTF-16 approximation.
- `lspLocation: Location?` — wraps `documentUri` + `lspPosition` into
an LSP Location, returning nil when `path` is empty.
Update all call sites.
When the client opts in to `workspace/tests/refresh` or
`workspace/playgrounds/refresh` via experimental client capabilities,
SourceKit-LSP now maintains a proactive cache of the current test and
playground lists and sends the corresponding `workspace/.../refresh`
notification whenever the cache changes. `workspaceTests()` /
`workspacePlaygrounds()` then serve subsequent fetch requests directly
from the cache.
Add `EntryPointManager`: runs background scans, stores the results,
fires callbacks on changes:
- Start scanning when build targets are updated including initial
updates, any watched files are changed, and index is updated.
- Send '/refresh' server initiated requests when the cache has changed.
- Coalesces rapid invalidations by cancelling any in-flight refresh task.
Also:
- Simplify `SourceKitIndexDelegate` from an `actor` with `AtomicInt32`
to a plain `class`, since it is only called from IndexStoreDB's
internal serial dispatch queue.
When semantic test discovery encounters XCTest methods in an extension it adds the extension definition to the root items list for each child test method. With N test methods, the same extension root appears N times, each copy receiving all N children. After merging, this produces N*N test items instead of N.
The bug is normally masked by the syntactic index, which provides correct results that take priority via combineTests(). This bug was exposed while I was debugging why tests generated by build plugins would appear multiple times. Turns out because they don't exist on disk at the time of the syntatic scan only the semantic results are used, which surfaced the issue.
The fix is to track already-added extension USRs to ensure each extension added once as a root item.