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..e2029d812 --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// +// 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` 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. + /// + /// 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/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") } diff --git a/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift new file mode 100644 index 000000000..2037800e4 --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// 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` 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. + /// + /// 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) + } +}