Skip to content

Commit 5c2055d

Browse files
authored
Merge pull request #1567 from lokesh-tr/macro-expansions-get-reference-document
Allow macro expansions to be viewed through `GetReferenceDocumentRequest` instead of storing in temporary files
2 parents 889a8db + 0522e1a commit 5c2055d

19 files changed

+557
-139
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,30 @@ export interface PeekDocumentsResult {
473473
success: boolean;
474474
}
475475
```
476+
477+
## `workspace/getReferenceDocument`
478+
479+
Request from the client to the server asking for contents of a URI having a custom scheme.
480+
For example: "sourcekit-lsp:"
481+
482+
Enable the experimental client capability `"workspace/getReferenceDocument"` so that the server responds with reference document URLs for certain requests or commands whenever possible.
483+
484+
- params: `GetReferenceDocumentParams`
485+
486+
- result: `GetReferenceDocumentResponse`
487+
488+
```ts
489+
export interface GetReferenceDocumentParams {
490+
/**
491+
* The `DocumentUri` of the custom scheme url for which content is required
492+
*/
493+
uri: DocumentUri;
494+
}
495+
496+
/**
497+
* Response containing `content` of `GetReferenceDocumentRequest`
498+
*/
499+
export interface GetReferenceDocumentResult {
500+
content: string;
501+
}
502+
```

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ add_library(LanguageServerProtocol STATIC
5555
Requests/ExecuteCommandRequest.swift
5656
Requests/FoldingRangeRequest.swift
5757
Requests/FormattingRequests.swift
58+
Requests/GetReferenceDocumentRequest.swift
5859
Requests/HoverRequest.swift
5960
Requests/ImplementationRequest.swift
6061
Requests/IndexedRenameRequest.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public let builtinRequests: [_RequestType.Type] = [
4848
DocumentTestsRequest.self,
4949
ExecuteCommandRequest.self,
5050
FoldingRangeRequest.self,
51+
GetReferenceDocumentRequest.self,
5152
HoverRequest.self,
5253
ImplementationRequest.self,
5354
InitializeRequest.self,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Request from the client to the server asking for contents of a URI having a custom scheme **(LSP Extension)**
14+
/// For example: "sourcekit-lsp:"
15+
///
16+
/// - Parameters:
17+
/// - uri: The `DocumentUri` of the custom scheme url for which content is required
18+
///
19+
/// - Returns: `GetReferenceDocumentResponse` which contains the `content` to be displayed.
20+
///
21+
/// ### LSP Extension
22+
///
23+
/// This request is an extension to LSP supported by SourceKit-LSP.
24+
/// Enable the experimental client capability `"workspace/getReferenceDocument"` so that the server responds with
25+
/// reference document URLs for certain requests or commands whenever possible.
26+
public struct GetReferenceDocumentRequest: RequestType {
27+
public static let method: String = "workspace/getReferenceDocument"
28+
public typealias Response = GetReferenceDocumentResponse
29+
30+
public var uri: DocumentURI
31+
32+
public init(uri: DocumentURI) {
33+
self.uri = uri
34+
}
35+
}
36+
37+
/// Response containing `content` of `GetReferenceDocumentRequest`
38+
public struct GetReferenceDocumentResponse: ResponseType {
39+
public var content: String
40+
41+
public init(content: String) {
42+
self.content = content
43+
}
44+
}

Sources/LanguageServerProtocol/SupportTypes/DocumentURI.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public struct DocumentURI: Codable, Hashable, Sendable {
3636
}
3737
}
3838

39+
/// The URL representation of the URI. Note that this URL can have an arbitrary scheme and might
40+
/// not represent a file URL.
41+
public var arbitrarySchemeURL: URL { storage }
42+
3943
/// The document's URL scheme, if present.
4044
public var scheme: String? {
4145
return storage.scheme

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ target_sources(SourceKitLSP PRIVATE
4747
Swift/ExpandMacroCommand.swift
4848
Swift/FoldingRange.swift
4949
Swift/MacroExpansion.swift
50+
Swift/MacroExpansionReferenceDocumentURLData.swift
5051
Swift/OpenInterface.swift
5152
Swift/RefactoringResponse.swift
5253
Swift/RefactoringEdit.swift
5354
Swift/RefactorCommand.swift
55+
Swift/ReferenceDocumentURL.swift
5456
Swift/RelatedIdentifiers.swift
5557
Swift/RewriteSourceKitPlaceholders.swift
5658
Swift/SemanticRefactorCommand.swift

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,10 @@ extension ClangLanguageService {
663663
func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny? {
664664
return try await forwardRequestToClangd(req)
665665
}
666+
667+
func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse {
668+
throw ResponseError.unknown("unsupported method")
669+
}
666670
}
667671

668672
/// Clang build settings derived from a `FileBuildSettingsChange`.

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ package protocol LanguageService: AnyObject, Sendable {
252252

253253
func executeCommand(_ req: ExecuteCommandRequest) async throws -> LSPAny?
254254

255+
func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse
256+
255257
/// Perform a syntactic scan of the file at the given URI for test cases and test classes.
256258
///
257259
/// This is used as a fallback to show the test cases in a file if the index for a given file is not up-to-date.

Sources/SourceKitLSP/Rename.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ extension SourceKitLSPServer {
687687
guard let workspace = await workspaceForDocument(uri: uri) else {
688688
throw ResponseError.workspaceNotOpen(uri)
689689
}
690-
guard let primaryFileLanguageService = workspace.documentService.value[uri] else {
690+
guard let primaryFileLanguageService = workspace.documentService(for: uri) else {
691691
return nil
692692
}
693693

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ package actor SourceKitLSPServer {
357357

358358
// This should be created as soon as we receive an open call, even if the document
359359
// isn't yet ready.
360-
guard let languageService = workspace.documentService.value[doc] else {
360+
guard let languageService = workspace.documentService(for: doc) else {
361361
return
362362
}
363363

@@ -377,7 +377,7 @@ package actor SourceKitLSPServer {
377377
guard let workspace = await self.workspaceForDocument(uri: request.textDocument.uri) else {
378378
throw ResponseError.workspaceNotOpen(request.textDocument.uri)
379379
}
380-
guard let languageService = workspace.documentService.value[doc] else {
380+
guard let languageService = workspace.documentService(for: doc) else {
381381
throw ResponseError.unknown("No language service for '\(request.textDocument.uri)' found")
382382
}
383383
return try await requestHandler(request, workspace, languageService)
@@ -400,7 +400,7 @@ package actor SourceKitLSPServer {
400400
guard let workspace = await self.workspaceForDocument(uri: documentUri) else {
401401
continue
402402
}
403-
guard workspace.documentService.value[documentUri] === languageService else {
403+
guard workspace.documentService(for: documentUri) === languageService else {
404404
continue
405405
}
406406
guard let snapshot = try? self.documentManager.latestSnapshot(documentUri) else {
@@ -518,7 +518,7 @@ package actor SourceKitLSPServer {
518518
_ language: Language,
519519
in workspace: Workspace
520520
) async -> LanguageService? {
521-
if let service = workspace.documentService.value[uri] {
521+
if let service = workspace.documentService(for: uri) {
522522
return service
523523
}
524524

@@ -536,17 +536,7 @@ package actor SourceKitLSPServer {
536536
"""
537537
)
538538

539-
return workspace.documentService.withLock { documentService in
540-
if let concurrentlySetService = documentService[uri] {
541-
// Since we await the construction of `service`, another call to this
542-
// function might have happened and raced us, setting
543-
// `workspace.documentServices[uri]`. If this is the case, return the
544-
// existing value and discard the service that we just retrieved.
545-
return concurrentlySetService
546-
}
547-
documentService[uri] = service
548-
return service
549-
}
539+
return workspace.setDocumentService(for: uri, service)
550540
}
551541
}
552542

@@ -733,6 +723,8 @@ extension SourceKitLSPServer: MessageHandler {
733723
await request.reply { try await executeCommand(request.params) }
734724
case let request as RequestAndReply<FoldingRangeRequest>:
735725
await self.handleRequest(for: request, requestHandler: self.foldingRange)
726+
case let request as RequestAndReply<GetReferenceDocumentRequest>:
727+
await request.reply { try await getReferenceDocument(request.params) }
736728
case let request as RequestAndReply<HoverRequest>:
737729
await self.handleRequest(for: request, requestHandler: self.hover)
738730
case let request as RequestAndReply<ImplementationRequest>:
@@ -804,7 +796,7 @@ extension SourceKitLSPServer: BuildSystemDelegate {
804796
continue
805797
}
806798

807-
guard let service = await self.workspaceForDocument(uri: uri)?.documentService.value[uri] else {
799+
guard let service = await self.workspaceForDocument(uri: uri)?.documentService(for: uri) else {
808800
continue
809801
}
810802

@@ -828,7 +820,7 @@ extension SourceKitLSPServer: BuildSystemDelegate {
828820
}
829821
for uri in self.affectedOpenDocumentsForChangeSet(changedFilesForWorkspace, self.documentManager) {
830822
logger.log("Dependencies updated for opened file \(uri.forLogging)")
831-
if let service = workspace.documentService.value[uri] {
823+
if let service = workspace.documentService(for: uri) {
832824
await service.documentDependenciesUpdated(uri)
833825
}
834826
}
@@ -964,6 +956,8 @@ extension SourceKitLSPServer {
964956
//
965957
// The below is a workaround for the vscode-swift extension since it cannot set client capabilities.
966958
// It passes "workspace/peekDocuments" through the `initializationOptions`.
959+
//
960+
// Similarly, for "workspace/getReferenceDocument".
967961
var clientCapabilities = req.capabilities
968962
if case .dictionary(let initializationOptions) = req.initializationOptions {
969963
if let peekDocuments = initializationOptions["workspace/peekDocuments"] {
@@ -975,6 +969,15 @@ extension SourceKitLSPServer {
975969
}
976970
}
977971

972+
if let getReferenceDocument = initializationOptions["workspace/getReferenceDocument"] {
973+
if case .dictionary(var experimentalCapabilities) = clientCapabilities.experimental {
974+
experimentalCapabilities["workspace/getReferenceDocument"] = getReferenceDocument
975+
clientCapabilities.experimental = .dictionary(experimentalCapabilities)
976+
} else {
977+
clientCapabilities.experimental = .dictionary(["workspace/getReferenceDocument": getReferenceDocument])
978+
}
979+
}
980+
978981
// The client announces what CodeLenses it supports, and the LSP will only return
979982
// ones found in the supportedCommands dictionary.
980983
if let codeLens = initializationOptions["textDocument/codeLens"],
@@ -1143,6 +1146,7 @@ extension SourceKitLSPServer {
11431146
"workspace/tests": .dictionary(["version": .int(2)]),
11441147
"textDocument/tests": .dictionary(["version": .int(2)]),
11451148
"workspace/triggerReindex": .dictionary(["version": .int(1)]),
1149+
"workspace/getReferenceDocument": .dictionary(["version": .int(1)]),
11461150
])
11471151
)
11481152
}
@@ -1342,7 +1346,7 @@ extension SourceKitLSPServer {
13421346
)
13431347
return
13441348
}
1345-
await workspace.documentService.value[uri]?.reopenDocument(notification)
1349+
await workspace.documentService(for: uri)?.reopenDocument(notification)
13461350
}
13471351

13481352
func closeDocument(_ notification: DidCloseTextDocumentNotification, workspace: Workspace) async {
@@ -1356,7 +1360,7 @@ extension SourceKitLSPServer {
13561360

13571361
await workspace.buildSystemManager.unregisterForChangeNotifications(for: uri)
13581362

1359-
await workspace.documentService.value[uri]?.closeDocument(notification)
1363+
await workspace.documentService(for: uri)?.closeDocument(notification)
13601364
}
13611365

13621366
func changeDocument(_ notification: DidChangeTextDocumentNotification) async {
@@ -1382,7 +1386,7 @@ extension SourceKitLSPServer {
13821386
// Already logged failure
13831387
return
13841388
}
1385-
await workspace.documentService.value[uri]?.changeDocument(
1389+
await workspace.documentService(for: uri)?.changeDocument(
13861390
notification,
13871391
preEditSnapshot: preEditSnapshot,
13881392
postEditSnapshot: postEditSnapshot,
@@ -1645,7 +1649,7 @@ extension SourceKitLSPServer {
16451649
guard let workspace = await workspaceForDocument(uri: uri) else {
16461650
throw ResponseError.workspaceNotOpen(uri)
16471651
}
1648-
guard let languageService = workspace.documentService.value[uri] else {
1652+
guard let languageService = workspace.documentService(for: uri) else {
16491653
return nil
16501654
}
16511655

@@ -1656,6 +1660,21 @@ extension SourceKitLSPServer {
16561660
return try await languageService.executeCommand(executeCommand)
16571661
}
16581662

1663+
func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse {
1664+
let referenceDocumentURL = try ReferenceDocumentURL(from: req.uri)
1665+
let primaryFileURI = referenceDocumentURL.primaryFile
1666+
1667+
guard let workspace = await workspaceForDocument(uri: primaryFileURI) else {
1668+
throw ResponseError.workspaceNotOpen(primaryFileURI)
1669+
}
1670+
1671+
guard let languageService = workspace.documentService(for: primaryFileURI) else {
1672+
throw ResponseError.unknown("No Language Service for URI: \(primaryFileURI)")
1673+
}
1674+
1675+
return try await languageService.getReferenceDocument(req)
1676+
}
1677+
16591678
func codeAction(
16601679
_ req: CodeActionRequest,
16611680
workspace: Workspace,

0 commit comments

Comments
 (0)