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() {} +} 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..4d8a5d5b8 --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/Playground.swift @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// 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"], + let location = Location(fromLSPAny: dictionary["location"]) + 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..38d704177 --- /dev/null +++ b/Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.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 `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"], + let range = Range(fromLSPAny: dictionary["range"]) + 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) + } +}