From b8de1f174a032ee3fa98e28fdd23566b15a0e657 Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Fri, 7 Nov 2025 15:03:08 -0500 Subject: [PATCH 1/3] 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/3] 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/3] 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. ///