From b8de1f174a032ee3fa98e28fdd23566b15a0e657 Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Fri, 7 Nov 2025 15:03:08 -0500 Subject: [PATCH 1/6] Add new playground types Needed to get playground support into SourceKit-LSP: https://github.com/swiftlang/sourcekit-lsp/pull/2340 --- .../SupportTypes/Location.swift | 24 ++++++- .../SupportTypes/Playground.swift | 69 +++++++++++++++++++ .../SupportTypes/TextDocumentPlayground.swift | 67 ++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 Sources/LanguageServerProtocol/SupportTypes/Playground.swift create mode 100644 Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift diff --git a/Sources/LanguageServerProtocol/SupportTypes/Location.swift b/Sources/LanguageServerProtocol/SupportTypes/Location.swift index e9903ae85..f57cf890f 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/Location.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/Location.swift @@ -13,7 +13,9 @@ /// Range within a particular document. /// /// For a location where the document is implied, use `Position` or `Range`. -public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConvertible, Comparable, Sendable { +public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConvertible, Comparable, Sendable, + LSPAnyCodable +{ public static func < (lhs: Location, rhs: Location) -> Bool { if lhs.uri != rhs.uri { return lhs.uri.stringValue < rhs.uri.stringValue @@ -34,7 +36,27 @@ public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConver self._range = CustomCodable(wrappedValue: range) } + public init?(fromLSPDictionary dictionary: [String: LSPAny]) { + guard + case .string(let uriString) = dictionary["uri"], + case .dictionary(let rangeDict) = dictionary["range"], + let uri = try? DocumentURI(string: uriString), + let range = Range(fromLSPDictionary: rangeDict) + else { + return nil + } + self.uri = uri + self._range = CustomCodable(wrappedValue: range) + } + public var debugDescription: String { return "\(uri):\(range.lowerBound)-\(range.upperBound)" } + + public func encodeToLSPAny() -> LSPAny { + return .dictionary([ + "uri": .string(uri.stringValue), + "range": range.encodeToLSPAny(), + ]) + } } diff --git a/Sources/LanguageServerProtocol/SupportTypes/Playground.swift b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift new file mode 100644 index 000000000..70e8a1aac --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// 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. +/// +/// **(LSP Extension)** +public struct Playground: ResponseType, Equatable, LSPAnyCodable { + /// 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`. + public var 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"`. + public var label: String? + + /// The location of where the #Playground macro was used in the source code. + public var location: Location + + public init( + id: String, + label: String?, + location: Location, + ) { + self.id = id + self.label = label + self.location = location + } + + public init?(fromLSPDictionary dictionary: [String: LSPAny]) { + guard + case .string(let id) = dictionary["id"], + case .dictionary(let locationDict) = dictionary["location"], + let location = Location(fromLSPDictionary: locationDict) + else { + return nil + } + self.id = id + self.location = location + if case .string(let label) = dictionary["label"] { + self.label = label + } + } + + public func encodeToLSPAny() -> LSPAny { + var dict: [String: LSPAny] = [ + "id": .string(id), + "location": location.encodeToLSPAny(), + ] + + if let label { + dict["label"] = .string(label) + } + + return .dictionary(dict) + } +} diff --git a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift new file mode 100644 index 000000000..b9eef4484 --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift @@ -0,0 +1,67 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// A `TextDocumentPlayground` item can be used to identify playground and identify it +/// to allow executing the playground through a "swift play" command. Differs from `Playground` +/// by only including the `range` instead of full `location` with the expectation being that +/// it is only returned as part of a textDocument/* request such as textDocument/codelens +public struct TextDocumentPlayground: ResponseType, Equatable, LSPAnyCodable { + /// 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`. + public var 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"`. + public var label: String? + + /// The full range of the #Playground macro body in the given file. + public var range: Range + + public init( + id: String, + label: String?, + range: Range + ) { + self.id = id + self.label = label + self.range = range + } + + public init?(fromLSPDictionary dictionary: [String: LSPAny]) { + guard + case .string(let id) = dictionary["id"], + case .dictionary(let rangeDict) = dictionary["range"], + let range = Range(fromLSPDictionary: rangeDict) + else { + return nil + } + self.id = id + self.range = range + if case .string(let label) = dictionary["label"] { + self.label = label + } + } + + public func encodeToLSPAny() -> LSPAny { + var dict: [String: LSPAny] = [ + "id": .string(id), + "range": range.encodeToLSPAny(), + ] + if let label { + dict["label"] = .string(label) + } + return .dictionary(dict) + } +} From 8372ff90bc13c706a37e99455df012cc22afcdbb Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Fri, 7 Nov 2025 15:42:25 -0500 Subject: [PATCH 2/6] Add supported codelens --- .../SupportTypes/SupportedCodeLensCommand.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/LanguageServerProtocol/SupportTypes/SupportedCodeLensCommand.swift b/Sources/LanguageServerProtocol/SupportTypes/SupportedCodeLensCommand.swift index 9f4248055..c5bb22d25 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/SupportedCodeLensCommand.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/SupportedCodeLensCommand.swift @@ -26,4 +26,7 @@ public struct SupportedCodeLensCommand: Codable, Hashable, RawRepresentable, Sen /// Lens to debug the application public static let debug: Self = Self(rawValue: "swift.debug") + + /// Lens to run the playground + public static let play: Self = Self(rawValue: "swift.play") } From be99be81598f2d62dac63a7e19d0520fd61c1166 Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Fri, 7 Nov 2025 16:15:00 -0500 Subject: [PATCH 3/6] Specify format of the playground `id` --- Sources/LanguageServerProtocol/SupportTypes/Playground.swift | 5 ++++- .../SupportTypes/TextDocumentPlayground.swift | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/Playground.swift b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift index 70e8a1aac..e2029d812 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/Playground.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift @@ -15,7 +15,10 @@ /// /// **(LSP Extension)** public struct Playground: ResponseType, Equatable, LSPAnyCodable { - /// Unique identifier for the `Playground`. Client can run the playground by executing `swift play `. + /// Unique identifier for the `Playground` with the format `/::[column]` where `target` + /// corresponds to the Swift package's target where the playground is defined, `filename` is the basename of the file + /// (not entire relative path), and `column` is optional only required if multiple playgrounds are defined on the same + /// line. Client can run the playground by executing `swift play `. /// /// This property is always present whether the `Playground` has a `label` or not. /// diff --git a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift index b9eef4484..2037800e4 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift @@ -15,7 +15,10 @@ /// by only including the `range` instead of full `location` with the expectation being that /// it is only returned as part of a textDocument/* request such as textDocument/codelens public struct TextDocumentPlayground: ResponseType, Equatable, LSPAnyCodable { - /// Unique identifier for the `Playground`. Client can run the playground by executing `swift play `. + /// Unique identifier for the `Playground` with the format `/::[column]` where `target` + /// corresponds to the Swift package's target where the playground is defined, `filename` is the basename of the file + /// (not entire relative path), and `column` is optional only required if multiple playgrounds are defined on the same + /// line. Client can run the playground by executing `swift play `. /// /// This property is always present whether the `Playground` has a `label` or not. /// From 9718c60470628e88ab76622b524198342713345d Mon Sep 17 00:00:00 2001 From: award999 Date: Mon, 10 Nov 2025 09:02:12 -0500 Subject: [PATCH 4/6] Update Sources/LanguageServerProtocol/SupportTypes/Playground.swift Co-authored-by: Alex Hoppen --- Sources/LanguageServerProtocol/SupportTypes/Playground.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/Playground.swift b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift index e2029d812..4d8a5d5b8 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/Playground.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift @@ -45,8 +45,7 @@ public struct Playground: ResponseType, Equatable, LSPAnyCodable { public init?(fromLSPDictionary dictionary: [String: LSPAny]) { guard case .string(let id) = dictionary["id"], - case .dictionary(let locationDict) = dictionary["location"], - let location = Location(fromLSPDictionary: locationDict) + let location = Location(fromLSPAny: dictionary["location"]) else { return nil } From d1d5facb1ce7dad4249e5daa9235a42d9fe1c879 Mon Sep 17 00:00:00 2001 From: award999 Date: Mon, 10 Nov 2025 09:02:42 -0500 Subject: [PATCH 5/6] Update Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift Co-authored-by: Alex Hoppen --- .../SupportTypes/TextDocumentPlayground.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift index 2037800e4..38d704177 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift @@ -45,8 +45,7 @@ public struct TextDocumentPlayground: ResponseType, Equatable, LSPAnyCodable { public init?(fromLSPDictionary dictionary: [String: LSPAny]) { guard case .string(let id) = dictionary["id"], - case .dictionary(let rangeDict) = dictionary["range"], - let range = Range(fromLSPDictionary: rangeDict) + let range = Range(fromLSPAny: dictionary["range"]) else { return nil } From 827b3a855db74761350b4aed53469afe859f0a96 Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Mon, 10 Nov 2025 10:00:24 -0500 Subject: [PATCH 6/6] Add workspace/playgrounds request too --- Sources/LanguageServerProtocol/CMakeLists.txt | 3 +++ Sources/LanguageServerProtocol/Messages.swift | 1 + .../WorkspacePlaygroundsRequest.swift | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 Sources/LanguageServerProtocol/Requests/WorkspacePlaygroundsRequest.swift diff --git a/Sources/LanguageServerProtocol/CMakeLists.txt b/Sources/LanguageServerProtocol/CMakeLists.txt index 8c4a6322c..18889e951 100644 --- a/Sources/LanguageServerProtocol/CMakeLists.txt +++ b/Sources/LanguageServerProtocol/CMakeLists.txt @@ -94,6 +94,7 @@ add_library(LanguageServerProtocol Requests/WillSaveWaitUntilTextDocumentRequest.swift Requests/WorkspaceDiagnosticsRequest.swift Requests/WorkspaceFoldersRequest.swift + Requests/WorkspacePlaygroundsRequest.swift Requests/WorkspaceSemanticTokensRefreshRequest.swift Requests/WorkspaceSymbolResolveRequest.swift Requests/WorkspaceSymbolsRequest.swift @@ -122,6 +123,7 @@ add_library(LanguageServerProtocol SupportTypes/NotebookDocument.swift SupportTypes/NotebookDocumentChangeEvent.swift SupportTypes/NotebookDocumentIdentifier.swift + SupportTypes/Playground.swift SupportTypes/Position.swift SupportTypes/PositionEncoding.swift SupportTypes/ProgressToken.swift @@ -138,6 +140,7 @@ add_library(LanguageServerProtocol SupportTypes/TextDocumentEdit.swift SupportTypes/TextDocumentIdentifier.swift SupportTypes/TextDocumentItem.swift + SupportTypes/TextDocumentPlayground.swift SupportTypes/TextDocumentSaveReason.swift SupportTypes/TextEdit.swift SupportTypes/Tracing.swift diff --git a/Sources/LanguageServerProtocol/Messages.swift b/Sources/LanguageServerProtocol/Messages.swift index 6318d6a08..923676dfe 100644 --- a/Sources/LanguageServerProtocol/Messages.swift +++ b/Sources/LanguageServerProtocol/Messages.swift @@ -87,6 +87,7 @@ public let builtinRequests: [_RequestType.Type] = [ WillSaveWaitUntilTextDocumentRequest.self, WorkspaceDiagnosticsRequest.self, WorkspaceFoldersRequest.self, + WorkspacePlaygroundsRequest.self, WorkspaceSemanticTokensRefreshRequest.self, WorkspaceSymbolResolveRequest.self, WorkspaceSymbolsRequest.self, diff --git a/Sources/LanguageServerProtocol/Requests/WorkspacePlaygroundsRequest.swift b/Sources/LanguageServerProtocol/Requests/WorkspacePlaygroundsRequest.swift new file mode 100644 index 000000000..c630a37a1 --- /dev/null +++ b/Sources/LanguageServerProtocol/Requests/WorkspacePlaygroundsRequest.swift @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// A request that returns the location and identifiers for all the #Playground macro playgrounds within the current workspace. +/// +/// **(LSP Extension)** +public struct WorkspacePlaygroundsRequest: LSPRequest, Hashable { + public static let method: String = "workspace/playgrounds" + public typealias Response = [Playground] + + public init() {} +}