From 001eb7e6b0a2d83ce7c09263b9216f2109c90381 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 12 Nov 2025 10:22:39 -0800 Subject: [PATCH] Revert "Parse #Playground macro expansions and include them in CodeLens request" --- Contributor Documentation/LSP Extensions.md | 46 -- Package.swift | 2 +- .../SKTestSupport/SwiftPMTestProject.swift | 4 +- Sources/SwiftLanguageService/CMakeLists.txt | 1 - .../DocumentFormatting.swift | 3 +- .../SwiftCodeLensScanner.swift | 55 +-- .../SwiftLanguageService.swift | 17 +- .../SwiftPlaygroundsScanner.swift | 101 ----- Sources/ToolchainRegistry/Toolchain.swift | 17 +- Tests/SourceKitLSPTests/CodeLensTests.swift | 396 +----------------- 10 files changed, 24 insertions(+), 618 deletions(-) delete mode 100644 Sources/SwiftLanguageService/SwiftPlaygroundsScanner.swift diff --git a/Contributor Documentation/LSP Extensions.md b/Contributor Documentation/LSP Extensions.md index 25e5a2f7a..d99af467b 100644 --- a/Contributor Documentation/LSP Extensions.md +++ b/Contributor Documentation/LSP Extensions.md @@ -690,52 +690,6 @@ export interface PeekDocumentsResult { } ``` -## `workspace/playgrounds` - -New request for returning the list of all #Playground macros in the workspace. - -Primarily designed to allow editors to provide a list of available playgrounds in the project workspace and allow -jumping to the locations where the #Playground macro was expanded. - -The request fetches the list of all macros found in the workspace, returning the location, identifier, and optional label -when available for each #Playground macro expansion. If you want to keep the list of playgrounds up to date without needing to -call `workspace/playgrounds` each time a document is changed, you can filter for `swift.play` CodeLens returned by the `textDocument/codelens` request. - -SourceKit-LSP will advertise `workspace/playgrounds` in its experimental server capabilities if it supports it. - -- params: `WorkspacePlaygroundParams` -- result: `Playground[]` - -```ts -export interface WorkspacePlaygroundParams {} - -/** - * A `Playground` represents a usage of the #Playground macro, providing the editor with the - * location of the playground and identifiers to allow executing the playground through a "swift play" command. - */ -export interface Playground { - /** - * Unique identifier for the `Playground`. Client can run the playground by executing `swift play `. - * - * This property is always present whether the `Playground` has a `label` or not. - * - * Follows the format output by `swift play --list`. - */ - id: string; - - /** - * The label that can be used as a display name for the playground. This optional property is only available - * for named playgrounds. For example: `#Playground("hello") { print("Hello!) }` would have a `label` of `"hello"`. - */ - label?: string - - /** - * The location of where the #Playground macro was used in the source code. - */ - location: Location -} -``` - ## `workspace/synchronize` Request from the client to the server to wait for SourceKit-LSP to handle all ongoing requests and, optionally, wait for background activity to finish. diff --git a/Package.swift b/Package.swift index b3cbfa518..d813ece96 100644 --- a/Package.swift +++ b/Package.swift @@ -800,7 +800,7 @@ var dependencies: [Package.Dependency] { .package(url: "https://github.com/swiftlang/swift-docc.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/swiftlang/swift-docc-symbolkit.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/swiftlang/swift-markdown.git", branch: relatedDependenciesBranch), - .package(url: "https://github.com/swiftlang/swift-tools-protocols.git", exact: "0.0.9"), + .package(url: "https://github.com/swiftlang/swift-tools-protocols.git", exact: "0.0.8"), .package(url: "https://github.com/swiftlang/swift-tools-support-core.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.1"), .package(url: "https://github.com/swiftlang/swift-syntax.git", branch: relatedDependenciesBranch), diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index 3d9135100..1883ce28f 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -17,7 +17,7 @@ package import SKOptions package import SourceKitLSP import SwiftExtensions import TSCBasic -package import ToolchainRegistry +import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import XCTest @@ -184,7 +184,6 @@ package class SwiftPMTestProject: MultiFileTestProject { initializationOptions: LSPAny? = nil, capabilities: ClientCapabilities = ClientCapabilities(), options: SourceKitLSPOptions? = nil, - toolchainRegistry: ToolchainRegistry = .forTesting, hooks: Hooks = Hooks(), enableBackgroundIndexing: Bool = false, usePullDiagnostics: Bool = true, @@ -226,7 +225,6 @@ package class SwiftPMTestProject: MultiFileTestProject { initializationOptions: initializationOptions, capabilities: capabilities, options: options, - toolchainRegistry: toolchainRegistry, hooks: hooks, enableBackgroundIndexing: enableBackgroundIndexing, usePullDiagnostics: usePullDiagnostics, diff --git a/Sources/SwiftLanguageService/CMakeLists.txt b/Sources/SwiftLanguageService/CMakeLists.txt index f6a1b3fb1..022405bc7 100644 --- a/Sources/SwiftLanguageService/CMakeLists.txt +++ b/Sources/SwiftLanguageService/CMakeLists.txt @@ -25,7 +25,6 @@ add_library(SwiftLanguageService STATIC InlayHints.swift MacroExpansion.swift OpenInterface.swift - SwiftPlaygroundsScanner.swift RefactoringEdit.swift RefactoringResponse.swift RelatedIdentifiers.swift diff --git a/Sources/SwiftLanguageService/DocumentFormatting.swift b/Sources/SwiftLanguageService/DocumentFormatting.swift index c4344684b..4ede6c313 100644 --- a/Sources/SwiftLanguageService/DocumentFormatting.swift +++ b/Sources/SwiftLanguageService/DocumentFormatting.swift @@ -21,7 +21,6 @@ import SwiftExtensions import SwiftParser import SwiftSyntax import TSCExtensions -import ToolchainRegistry @_spi(SourceKitLSP) import ToolsProtocolsSwiftExtensions import struct TSCBasic.AbsolutePath @@ -172,7 +171,7 @@ extension SwiftLanguageService { options: FormattingOptions, range: Range? = nil ) async throws -> [TextEdit]? { - guard let swiftFormat = toolchain.swiftFormat else { + guard let swiftFormat else { throw ResponseError.unknown( "Formatting not supported because the toolchain is missing the swift-format executable" ) diff --git a/Sources/SwiftLanguageService/SwiftCodeLensScanner.swift b/Sources/SwiftLanguageService/SwiftCodeLensScanner.swift index b058a65d1..d0a29c92b 100644 --- a/Sources/SwiftLanguageService/SwiftCodeLensScanner.swift +++ b/Sources/SwiftLanguageService/SwiftCodeLensScanner.swift @@ -10,12 +10,9 @@ // //===----------------------------------------------------------------------===// -internal import BuildServerIntegration -import BuildServerProtocol @_spi(SourceKitLSP) import LanguageServerProtocol import SourceKitLSP import SwiftSyntax -import ToolchainRegistry /// Scans a source file for classes or structs annotated with `@main` and returns a code lens for them. final class SwiftCodeLensScanner: SyntaxVisitor { @@ -45,57 +42,19 @@ final class SwiftCodeLensScanner: SyntaxVisitor { /// and returns CodeLens's with Commands to run/debug the application. public static func findCodeLenses( in snapshot: DocumentSnapshot, - workspace: Workspace?, syntaxTreeManager: SyntaxTreeManager, - supportedCommands: [SupportedCodeLensCommand: String], - toolchain: Toolchain + targetName: String? = nil, + supportedCommands: [SupportedCodeLensCommand: String] ) async -> [CodeLens] { - guard !supportedCommands.isEmpty else { + guard snapshot.text.contains("@main") && !supportedCommands.isEmpty else { + // This is intended to filter out files that obviously do not contain an entry point. return [] } - var targetDisplayName: String? = nil - if let workspace, - let target = await workspace.buildServerManager.canonicalTarget(for: snapshot.uri), - let buildTarget = await workspace.buildServerManager.buildTarget(named: target) - { - targetDisplayName = buildTarget.displayName - } - - var codeLenses: [CodeLens] = [] let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot) - if snapshot.text.contains("@main") { - let visitor = SwiftCodeLensScanner( - snapshot: snapshot, - targetName: targetDisplayName, - supportedCommands: supportedCommands - ) - visitor.walk(syntaxTree) - codeLenses += visitor.result - } - - // "swift.play" CodeLens should be ignored if "swift-play" is not in the toolchain as the client has no way of running - if toolchain.swiftPlay != nil, let workspace, let playCommand = supportedCommands[SupportedCodeLensCommand.play], - snapshot.text.contains("#Playground") - { - let playgrounds = await SwiftPlaygroundsScanner.findDocumentPlaygrounds( - in: syntaxTree, - workspace: workspace, - snapshot: snapshot - ) - codeLenses += playgrounds.map({ - CodeLens( - range: $0.range, - command: Command( - title: "Play \"\($0.label ?? $0.id)\"", - command: playCommand, - arguments: [$0.encodeToLSPAny()] - ) - ) - }) - } - - return codeLenses + let visitor = SwiftCodeLensScanner(snapshot: snapshot, targetName: targetName, supportedCommands: supportedCommands) + visitor.walk(syntaxTree) + return visitor.result } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 5e647d20f..d76c4c697 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -107,7 +107,7 @@ package actor SwiftLanguageService: LanguageService, Sendable { package let sourcekitd: SourceKitD /// Path to the swift-format executable if it exists in the toolchain. - let toolchain: Toolchain + let swiftFormat: URL? /// Queue on which notifications from sourcekitd are handled to ensure we are /// handling them in-order. @@ -213,7 +213,7 @@ package actor SwiftLanguageService: LanguageService, Sendable { } self.sourcekitdPath = sourcekitd self.sourceKitLSPServer = sourceKitLSPServer - self.toolchain = toolchain + self.swiftFormat = toolchain.swiftFormat let pluginPaths: PluginPaths? if let clientPlugin = options.sourcekitdOrDefault.clientPlugin, let servicePlugin = options.sourcekitdOrDefault.servicePlugin @@ -1032,13 +1032,18 @@ extension SwiftLanguageService { package func codeLens(_ req: CodeLensRequest) async throws -> [CodeLens] { let snapshot = try documentManager.latestSnapshot(req.textDocument.uri) - let workspace = await sourceKitLSPServer?.workspaceForDocument(uri: req.textDocument.uri) + var targetDisplayName: String? = nil + if let workspace = await sourceKitLSPServer?.workspaceForDocument(uri: req.textDocument.uri), + let target = await workspace.buildServerManager.canonicalTarget(for: req.textDocument.uri), + let buildTarget = await workspace.buildServerManager.buildTarget(named: target) + { + targetDisplayName = buildTarget.displayName + } return await SwiftCodeLensScanner.findCodeLenses( in: snapshot, - workspace: workspace, syntaxTreeManager: self.syntaxTreeManager, - supportedCommands: self.capabilityRegistry.supportedCodeLensCommands, - toolchain: toolchain + targetName: targetDisplayName, + supportedCommands: self.capabilityRegistry.supportedCodeLensCommands ) } diff --git a/Sources/SwiftLanguageService/SwiftPlaygroundsScanner.swift b/Sources/SwiftLanguageService/SwiftPlaygroundsScanner.swift deleted file mode 100644 index b7ad4457f..000000000 --- a/Sources/SwiftLanguageService/SwiftPlaygroundsScanner.swift +++ /dev/null @@ -1,101 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -internal import BuildServerIntegration -import Foundation -@_spi(SourceKitLSP) import LanguageServerProtocol -import SKLogging -import SourceKitLSP -import SwiftParser -import SwiftSyntax - -// MARK: - SwiftPlaygroundsScanner - -final class SwiftPlaygroundsScanner: SyntaxVisitor { - /// The base ID to use to generate IDs for any playgrounds found in this file. - private let baseID: String - - /// The snapshot of the document for which we are getting playgrounds. - private let snapshot: DocumentSnapshot - - /// Accumulating the result in here. - private var result: [TextDocumentPlayground] = [] - - /// Keep track of if "Playgrounds" has been imported - private var isPlaygroundImported: Bool = false - - private init(baseID: String, snapshot: DocumentSnapshot) { - self.baseID = baseID - self.snapshot = snapshot - super.init(viewMode: .sourceAccurate) - } - - /// Designated entry point for `SwiftPlaygroundsScanner`. - static func findDocumentPlaygrounds( - in node: some SyntaxProtocol, - workspace: Workspace, - snapshot: DocumentSnapshot - ) async -> [TextDocumentPlayground] { - guard let canonicalTarget = await workspace.buildServerManager.canonicalTarget(for: snapshot.uri), - let moduleName = await workspace.buildServerManager.moduleName(for: snapshot.uri, in: canonicalTarget), - let baseName = snapshot.uri.fileURL?.lastPathComponent - else { - return [] - } - let visitor = SwiftPlaygroundsScanner(baseID: "\(moduleName)/\(baseName)", snapshot: snapshot) - visitor.walk(node) - return visitor.isPlaygroundImported ? visitor.result : [] - } - - /// Add a playground location with the given parameters to the `result` array. - private func record( - id: String, - label: String?, - range: Range - ) { - let positionRange = snapshot.absolutePositionRange(of: range) - - result.append( - TextDocumentPlayground( - id: id, - label: label, - range: positionRange, - ) - ) - } - - override func visit(_ node: ImportPathComponentSyntax) -> SyntaxVisitorContinueKind { - if node.name.text == "Playgrounds" { - isPlaygroundImported = true - } - return .skipChildren - } - - override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { - guard node.macroName.text == "Playground" else { - return .skipChildren - } - - let startPosition = snapshot.sourcekitdPosition(of: snapshot.position(of: node.positionAfterSkippingLeadingTrivia)) - let stringLiteral = node.arguments.first?.expression.as(StringLiteralExprSyntax.self) - let playgroundLabel = stringLiteral?.representedLiteralValue - let playgroundID = "\(baseID):\(startPosition.line):\(startPosition.utf8Column)" - - record( - id: playgroundID, - label: playgroundLabel, - range: node.trimmedRange - ) - - return .skipChildren - } -} diff --git a/Sources/ToolchainRegistry/Toolchain.swift b/Sources/ToolchainRegistry/Toolchain.swift index a64021890..d10bcc13a 100644 --- a/Sources/ToolchainRegistry/Toolchain.swift +++ b/Sources/ToolchainRegistry/Toolchain.swift @@ -89,9 +89,6 @@ public final class Toolchain: Sendable { /// The path to the swift-format executable, if available. package let swiftFormat: URL? - /// The path to the swift-play executable, if available. - package let swiftPlay: URL? - /// The path to the clangd language server if available. package let clangd: URL? @@ -206,7 +203,6 @@ public final class Toolchain: Sendable { swift: URL? = nil, swiftc: URL? = nil, swiftFormat: URL? = nil, - swiftPlay: URL? = nil, clangd: URL? = nil, sourcekitd: URL? = nil, sourceKitClientPlugin: URL? = nil, @@ -220,7 +216,6 @@ public final class Toolchain: Sendable { self.swift = swift self.swiftc = swiftc self.swiftFormat = swiftFormat - self.swiftPlay = swiftPlay self.clangd = clangd self.sourcekitd = sourcekitd self.sourceKitClientPlugin = sourceKitClientPlugin @@ -245,9 +240,7 @@ public final class Toolchain: Sendable { } } return isSuperset(for: \.clang) && isSuperset(for: \.swift) && isSuperset(for: \.swiftc) - && isSuperset(for: \.swiftPlay) && isSuperset(for: \.swiftFormat) && isSuperset(for: \.sourceKitClientPlugin) - && isSuperset(for: \.sourceKitServicePlugin) && isSuperset(for: \.clangd) && isSuperset(for: \.sourcekitd) - && isSuperset(for: \.libIndexStore) + && isSuperset(for: \.clangd) && isSuperset(for: \.sourcekitd) && isSuperset(for: \.libIndexStore) } /// Same as `isSuperset` but returns `false` if both toolchains have the same set of tools. @@ -285,7 +278,6 @@ public final class Toolchain: Sendable { var swift: URL? = nil var swiftc: URL? = nil var swiftFormat: URL? = nil - var swiftPlay: URL? = nil var sourcekitd: URL? = nil var sourceKitClientPlugin: URL? = nil var sourceKitServicePlugin: URL? = nil @@ -345,12 +337,6 @@ public final class Toolchain: Sendable { foundAny = true } - let swiftPlayPath = binPath.appending(component: "swift-play\(execExt)") - if FileManager.default.isExecutableFile(atPath: swiftPlayPath.path) { - swiftPlay = swiftPlayPath - foundAny = true - } - // If 'currentPlatform' is nil it's most likely an unknown linux flavor. let dylibExtension: String if let dynamicLibraryExtension = Platform.current?.dynamicLibraryExtension { @@ -421,7 +407,6 @@ public final class Toolchain: Sendable { swift: swift, swiftc: swiftc, swiftFormat: swiftFormat, - swiftPlay: swiftPlay, clangd: clangd, sourcekitd: sourcekitd, sourceKitClientPlugin: sourceKitClientPlugin, diff --git a/Tests/SourceKitLSPTests/CodeLensTests.swift b/Tests/SourceKitLSPTests/CodeLensTests.swift index cba757da6..917e82454 100644 --- a/Tests/SourceKitLSPTests/CodeLensTests.swift +++ b/Tests/SourceKitLSPTests/CodeLensTests.swift @@ -13,58 +13,9 @@ @_spi(SourceKitLSP) import LanguageServerProtocol import SKLogging import SKTestSupport -import ToolchainRegistry import XCTest -fileprivate extension Toolchain { - #if compiler(>=6.4) - #warning( - "Once we require swift-play in the toolchain that's used to test SourceKit-LSP, we can just use `forTesting`" - ) - #endif - static var forTestingWithSwiftPlay: Toolchain { - get async throws { - let toolchain = try await unwrap(ToolchainRegistry.forTesting.default) - return Toolchain( - identifier: "\(toolchain.identifier)-swift-swift", - displayName: "\(toolchain.identifier) with swift-play", - path: toolchain.path, - clang: toolchain.clang, - swift: toolchain.swift, - swiftc: toolchain.swiftc, - swiftPlay: URL(fileURLWithPath: "/dummy/usr/bin/swift-play"), - clangd: toolchain.clangd, - sourcekitd: toolchain.sourcekitd, - sourceKitClientPlugin: toolchain.sourceKitClientPlugin, - sourceKitServicePlugin: toolchain.sourceKitServicePlugin, - libIndexStore: toolchain.libIndexStore - ) - } - } - - static var forTestingWithoutSwiftPlay: Toolchain { - get async throws { - let toolchain = try await unwrap(ToolchainRegistry.forTesting.default) - return Toolchain( - identifier: "\(toolchain.identifier)-no-swift-swift", - displayName: "\(toolchain.identifier) without swift-play", - path: toolchain.path, - clang: toolchain.clang, - swift: toolchain.swift, - swiftc: toolchain.swiftc, - swiftPlay: nil, - clangd: toolchain.clangd, - sourcekitd: toolchain.sourcekitd, - sourceKitClientPlugin: toolchain.sourceKitClientPlugin, - sourceKitServicePlugin: toolchain.sourceKitServicePlugin, - libIndexStore: toolchain.libIndexStore - ) - } - } -} - final class CodeLensTests: SourceKitLSPTestCase { - func testNoLenses() async throws { var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens() codeLensCapabilities.supportedCommands = [ @@ -93,26 +44,15 @@ final class CodeLensTests: SourceKitLSPTestCase { } func testNoClientCodeLenses() async throws { - let toolchainRegistry = ToolchainRegistry(toolchains: [try await Toolchain.forTestingWithSwiftPlay]) let project = try await SwiftPMTestProject( files: [ "Test.swift": """ - import Playgrounds @main struct MyApp { public static func main() {} } - - #Playground { - print("Hello Playground!") - } - - #Playground("named") { - print("Hello named Playground!") - } """ - ], - toolchainRegistry: toolchainRegistry + ] ) let (uri, _) = try project.openDocument("Test.swift") @@ -129,27 +69,16 @@ final class CodeLensTests: SourceKitLSPTestCase { codeLensCapabilities.supportedCommands = [ SupportedCodeLensCommand.run: "swift.run", SupportedCodeLensCommand.debug: "swift.debug", - SupportedCodeLensCommand.play: "swift.play", ] let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities)) - let toolchainRegistry = ToolchainRegistry(toolchains: [try await Toolchain.forTestingWithSwiftPlay]) let project = try await SwiftPMTestProject( files: [ "Sources/MyApp/Test.swift": """ - import Playgrounds 1️⃣@main2️⃣ struct MyApp { public static func main() {} } - - 3️⃣#Playground { - print("Hello Playground!") - }4️⃣ - - 5️⃣#Playground("named") { - print("Hello named Playground!") - }6️⃣ """ ], manifest: """ @@ -162,159 +91,7 @@ final class CodeLensTests: SourceKitLSPTestCase { targets: [.executableTarget(name: "MyApp")] ) """, - capabilities: capabilities, - toolchainRegistry: toolchainRegistry - ) - - let (uri, positions) = try project.openDocument("Test.swift") - - let response = try await project.testClient.send( - CodeLensRequest(textDocument: TextDocumentIdentifier(uri)) - ) - - XCTAssertEqual( - response, - [ - CodeLens( - range: positions["1️⃣"].. String { - "bar" - } - - #Playground("foo") { - print(foo()) - } - - #Playground { - print(foo()) - } - - public func bar(_ i: Int, _ j: Int) -> Int { - i + j - } - - #Playground("bar") { - var i = bar(1, 2) - i = i + 1 - print(i) - } - """ - ], - capabilities: capabilities, - toolchainRegistry: toolchainRegistry - ) - - let (uri, _) = try project.openDocument("Test.swift") - let response = try await project.testClient.send( - CodeLensRequest(textDocument: TextDocumentIdentifier(uri)) - ) - XCTAssertEqual(response, []) - } - - func testCodeLensRequestNoPlaygrounds() async throws { - var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens() - codeLensCapabilities.supportedCommands = [ - SupportedCodeLensCommand.play: "swift.play" - ] - let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities)) - let toolchainRegistry = ToolchainRegistry(toolchains: [try await Toolchain.forTestingWithSwiftPlay]) - let project = try await SwiftPMTestProject( - files: [ - "Sources/MyLibrary/Test.swift": """ - import Playgrounds - - public func Playground(_ i: Int, _ j: Int) -> Int { - i + j - } - - @Playground - struct MyPlayground { - public var playground: String = "" - } - """ - ], - capabilities: capabilities, - toolchainRegistry: toolchainRegistry - ) - - let (uri, _) = try project.openDocument("Test.swift") - let response = try await project.testClient.send( - CodeLensRequest(textDocument: TextDocumentIdentifier(uri)) - ) - XCTAssertEqual(response, []) - } - - func testEmojiPlaygroundName() async throws { - var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens() - codeLensCapabilities.supportedCommands = [ - SupportedCodeLensCommand.play: "swift.play" - ] - let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities)) - let toolchainRegistry = ToolchainRegistry(toolchains: [try await Toolchain.forTestingWithSwiftPlay]) - - let project = try await SwiftPMTestProject( - files: [ - "Sources/MyLibrary/Test.swift": """ - import Playgrounds - 1️⃣#Playground("🧑‍🧑‍🧒‍🧒") { print("Hello Playground!") }2️⃣ - """ - ], - capabilities: capabilities, - toolchainRegistry: toolchainRegistry - ) - - let (uri, positions) = try project.openDocument("Test.swift") - - let response = try await project.testClient.send( - CodeLensRequest(textDocument: TextDocumentIdentifier(uri)) - ) - - XCTAssertEqual( - response, - [ - CodeLens( - range: positions["1️⃣"]..