Skip to content

Commit 565edf9

Browse files
committed
Address review comments
1 parent 3d67401 commit 565edf9

File tree

6 files changed

+121
-35
lines changed

6 files changed

+121
-35
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,12 +692,12 @@ export interface PeekDocumentsResult {
692692
693693
## `workspace/playgrounds`
694694
695-
New request for return the list of all #Playground macro expansions in the workspace.
695+
New request for returning the list of all #Playground macros in the workspace.
696696
697697
Primarily designed to allow editors to provide a list of available playgrounds in the project workspace and allow
698698
jumping to the locations where the #Playground macro was expanded.
699699
700-
The request fetches the list of all macro expansions found in the workspace, returning the location, identifier, and optional label
700+
The request fetches the list of all macros found in the workspace, returning the location, identifier, and optional label
701701
when available for each #Playground macro expansion. If you want to keep the list of playgrounds up to date without needing to
702702
call `workspace/playgrounds` each time a document is changed, you can filter for `swift.play` CodeLens returned by the `textDocument/codelens` request.
703703
@@ -710,7 +710,7 @@ SourceKit-LSP will advertise `workspace/playgrounds` in its experimental server
710710
export interface WorkspacePlaygroundParams {}
711711

712712
/**
713-
* A `Playground` represents an expansion of the #Playground macro, providing the editor with the
713+
* A `Playground` represents a usage of the #Playground macro, providing the editor with the
714714
* location of the playground and identifiers to allow executing the playground through a "swift play" command.
715715
*/
716716
export interface Playground {
@@ -730,7 +730,7 @@ export interface Playground {
730730
label?: string
731731

732732
/**
733-
* The location of the of where the #Playground macro expansion occured in the source code.
733+
* The location of where the #Playground macro was used in the source code.
734734
*/
735735
location: Location
736736
}

Sources/LanguageServerProtocol/SupportTypes/Location.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
/// Range within a particular document.
1414
///
1515
/// For a location where the document is implied, use `Position` or `Range<Position>`.
16-
public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConvertible, Comparable, Sendable {
16+
public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConvertible, Comparable, Sendable,
17+
LSPAnyCodable
18+
{
1719
public static func < (lhs: Location, rhs: Location) -> Bool {
1820
if lhs.uri != rhs.uri {
1921
return lhs.uri.stringValue < rhs.uri.stringValue
@@ -34,14 +36,27 @@ public struct Location: ResponseType, Hashable, Codable, CustomDebugStringConver
3436
self._range = CustomCodable<PositionRange>(wrappedValue: range)
3537
}
3638

39+
public init?(fromLSPDictionary dictionary: [String: LSPAny]) {
40+
guard
41+
case .string(let uriString) = dictionary["uri"],
42+
case .dictionary(let rangeDict) = dictionary["range"],
43+
let uri = try? DocumentURI(string: uriString),
44+
let range = Range<Position>(fromLSPDictionary: rangeDict)
45+
else {
46+
return nil
47+
}
48+
self.uri = uri
49+
self._range = CustomCodable<PositionRange>(wrappedValue: range)
50+
}
51+
3752
public var debugDescription: String {
3853
return "\(uri):\(range.lowerBound)-\(range.upperBound)"
3954
}
4055

4156
public func encodeToLSPAny() -> LSPAny {
4257
return .dictionary([
4358
"uri": .string(uri.stringValue),
44-
"range": range.encodeToLSPAny()
59+
"range": range.encodeToLSPAny(),
4560
])
4661
}
4762
}

Sources/LanguageServerProtocol/SupportTypes/Playground.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,23 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
/// A playground item that can be used to identify playgrounds alongside a source file.
14-
public struct Playground: ResponseType, Equatable {
15-
/// Identifier for the `Playground`.
13+
/// A `Playground` represents a usage of the #Playground macro, providing the editor with the
14+
/// location of the playground and identifiers to allow executing the playground through a "swift play" command.
15+
///
16+
/// **(LSP Extension)**
17+
public struct Playground: ResponseType, Equatable, LSPAnyCodable {
18+
/// Unique identifier for the `Playground`. Client can run the playground by executing `swift play <id>`.
1619
///
17-
/// This identifier uniquely identifies the playground. It can be used to run an individual playground with `swift play`.
20+
/// This property is always present whether the `Playground` has a `label` or not.
21+
///
22+
/// Follows the format output by `swift play --list`.
1823
public var id: String
1924

20-
/// Display name describing the playground.
25+
/// The label that can be used as a display name for the playground. This optional property is only available
26+
/// for named playgrounds. For example: `#Playground("hello") { print("Hello!) }` would have a `label` of `"hello"`.
2127
public var label: String?
2228

23-
/// The location of the #Playground macro expansion in the source code.
29+
/// The location of where the #Playground macro was used in the source code.
2430
public var location: Location
2531

2632
public init(
@@ -33,10 +39,25 @@ public struct Playground: ResponseType, Equatable {
3339
self.location = location
3440
}
3541

42+
public init?(fromLSPDictionary dictionary: [String: LSPAny]) {
43+
guard
44+
case .string(let id) = dictionary["id"],
45+
case .dictionary(let locationDict) = dictionary["location"],
46+
let location = Location(fromLSPDictionary: locationDict)
47+
else {
48+
return nil
49+
}
50+
self.id = id
51+
self.location = location
52+
if case .string(let label) = dictionary["label"] {
53+
self.label = label
54+
}
55+
}
56+
3657
public func encodeToLSPAny() -> LSPAny {
3758
var dict: [String: LSPAny] = [
3859
"id": .string(id),
39-
"location": location.encodeToLSPAny()
60+
"location": location.encodeToLSPAny(),
4061
]
4162

4263
if let label {

Sources/LanguageServerProtocol/SupportTypes/TextDocumentPlayground.swift

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
/// A playground item that can be used to identify playground. Differs from `TextDocumentPlayground`
14-
/// by not including location which is given for `textDocument/playgrounds` request
15-
public struct TextDocumentPlayground: ResponseType, Equatable {
16-
/// Identifier for the `TextDocumentPlayground`.
13+
/// A `TextDocumentPlayground` item can be used to identify playground and identify it
14+
/// to allow executing the playground through a "swift play" command. Differs from `Playground`
15+
/// by only including the `range` instead of full `location` with the expectation being that
16+
/// it is only returned as part of a textDocument/* request such as textDocument/codelens
17+
public struct TextDocumentPlayground: ResponseType, Equatable, LSPAnyCodable {
18+
/// Unique identifier for the `Playground`. Client can run the playground by executing `swift play <id>`.
1719
///
18-
/// This identifier uniquely identifies the playground. It can be used to run an individual playground with `swift play`.
20+
/// This property is always present whether the `Playground` has a `label` or not.
21+
///
22+
/// Follows the format output by `swift play --list`.
1923
public var id: String
2024

21-
/// Display name describing the playground.
25+
/// The label that can be used as a display name for the playground. This optional property is only available
26+
/// for named playgrounds. For example: `#Playground("hello") { print("Hello!) }` would have a `label` of `"hello"`.
2227
public var label: String?
2328

24-
/// The range of the #Playground macro expansion in the given file.
29+
/// The full range of the #Playground macro body in the given file.
2530
public var range: Range<Position>
2631

2732
public init(
@@ -34,16 +39,29 @@ public struct TextDocumentPlayground: ResponseType, Equatable {
3439
self.range = range
3540
}
3641

42+
public init?(fromLSPDictionary dictionary: [String: LSPAny]) {
43+
guard
44+
case .string(let id) = dictionary["id"],
45+
case .dictionary(let rangeDict) = dictionary["range"],
46+
let range = Range<Position>(fromLSPDictionary: rangeDict)
47+
else {
48+
return nil
49+
}
50+
self.id = id
51+
self.range = range
52+
if case .string(let label) = dictionary["label"] {
53+
self.label = label
54+
}
55+
}
56+
3757
public func encodeToLSPAny() -> LSPAny {
3858
var dict: [String: LSPAny] = [
3959
"id": .string(id),
40-
"range": range.encodeToLSPAny()
60+
"range": range.encodeToLSPAny(),
4161
]
42-
4362
if let label {
4463
dict["label"] = .string(label)
4564
}
46-
4765
return .dictionary(dict)
4866
}
4967
}

Sources/SwiftLanguageService/SwiftCodeLensScanner.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,34 @@ final class SwiftCodeLensScanner: SyntaxVisitor {
6565
var codeLenses: [CodeLens] = []
6666
let syntaxTree = await syntaxTreeManager.syntaxTree(for: snapshot)
6767
if snapshot.text.contains("@main") {
68-
let visitor = SwiftCodeLensScanner(snapshot: snapshot, targetName: targetDisplayName, supportedCommands: supportedCommands)
68+
let visitor = SwiftCodeLensScanner(
69+
snapshot: snapshot,
70+
targetName: targetDisplayName,
71+
supportedCommands: supportedCommands
72+
)
6973
visitor.walk(syntaxTree)
7074
codeLenses += visitor.result
7175
}
7276

7377
// "swift.play" CodeLens should be ignored if "swift-play" is not in the toolchain as the client has no way of running
74-
if toolchain.swiftPlay != nil, let workspace, let playCommand = supportedCommands[SupportedCodeLensCommand.play], snapshot.text.contains("#Playground") {
75-
let playgrounds = await SwiftPlaygroundsScanner.findDocumentPlaygrounds(in: syntaxTree, workspace: workspace, snapshot: snapshot)
76-
codeLenses += playgrounds.map({ CodeLens(range: $0.range, command: Command(title: "Play \"\($0.label ?? $0.id)\"", command: playCommand, arguments: [$0.encodeToLSPAny()])) })
78+
if toolchain.swiftPlay != nil, let workspace, let playCommand = supportedCommands[SupportedCodeLensCommand.play],
79+
snapshot.text.contains("#Playground")
80+
{
81+
let playgrounds = await SwiftPlaygroundsScanner.findDocumentPlaygrounds(
82+
in: syntaxTree,
83+
workspace: workspace,
84+
snapshot: snapshot
85+
)
86+
codeLenses += playgrounds.map({
87+
CodeLens(
88+
range: $0.range,
89+
command: Command(
90+
title: "Play \"\($0.label ?? $0.id)\"",
91+
command: playCommand,
92+
arguments: [$0.encodeToLSPAny()]
93+
)
94+
)
95+
})
7796
}
7897

7998
return codeLenses

Tests/SourceKitLSPTests/CodeLensTests.swift

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ final class CodeLensTests: XCTestCase {
1919

2020
var toolchain: Toolchain!
2121
var toolchainWithSwiftPlay: Toolchain!
22-
22+
2323
override func setUp() async throws {
2424
toolchain = try await unwrap(ToolchainRegistry.forTesting.default)
2525
toolchainWithSwiftPlay = Toolchain(
@@ -29,7 +29,7 @@ final class CodeLensTests: XCTestCase {
2929
clang: toolchain.clang,
3030
swift: toolchain.swift,
3131
swiftc: toolchain.swiftc,
32-
swiftPlay: URL(string: "/path/to/swift-play"),
32+
swiftPlay: URL(fileURLWithPath: "/dummy/usr/bin/swift-play"),
3333
clangd: toolchain.clangd,
3434
sourcekitd: toolchain.sourcekitd,
3535
sourceKitClientPlugin: toolchain.sourceKitClientPlugin,
@@ -191,7 +191,7 @@ final class CodeLensTests: XCTestCase {
191191
func testMultiplePlaygroundCodeLensOnLine() async throws {
192192
var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens()
193193
codeLensCapabilities.supportedCommands = [
194-
SupportedCodeLensCommand.play: "swift.play",
194+
SupportedCodeLensCommand.play: "swift.play"
195195
]
196196
let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities))
197197
let toolchainRegistry = ToolchainRegistry(toolchains: [toolchainWithSwiftPlay])
@@ -266,8 +266,21 @@ final class CodeLensTests: XCTestCase {
266266
SupportedCodeLensCommand.play: "swift.play",
267267
]
268268
let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities))
269-
let toolchain = try await unwrap(ToolchainRegistry.forTesting.default)
270-
let toolchainRegistry = ToolchainRegistry(toolchains: [toolchain])
269+
let toolchainWithoutSwiftPlay = Toolchain(
270+
identifier: "\(toolchain.identifier)-swift-swift",
271+
displayName: "\(toolchain.identifier) with swift-play",
272+
path: toolchain.path,
273+
clang: toolchain.clang,
274+
swift: toolchain.swift,
275+
swiftc: toolchain.swiftc,
276+
swiftPlay: nil,
277+
clangd: toolchain.clangd,
278+
sourcekitd: toolchain.sourcekitd,
279+
sourceKitClientPlugin: toolchain.sourceKitClientPlugin,
280+
sourceKitServicePlugin: toolchain.sourceKitServicePlugin,
281+
libIndexStore: toolchain.libIndexStore
282+
)
283+
let toolchainRegistry = ToolchainRegistry(toolchains: [toolchainWithoutSwiftPlay])
271284

272285
let project = try await SwiftPMTestProject(
273286
files: [
@@ -317,15 +330,15 @@ final class CodeLensTests: XCTestCase {
317330
CodeLens(
318331
range: positions["1️⃣"]..<positions["2️⃣"],
319332
command: Command(title: "Debug MyApp", command: "swift.debug", arguments: [.string("MyApp")])
320-
)
333+
),
321334
]
322335
)
323336
}
324337

325338
func testNoImportPlaygrounds() async throws {
326339
var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens()
327340
codeLensCapabilities.supportedCommands = [
328-
SupportedCodeLensCommand.play: "swift.play",
341+
SupportedCodeLensCommand.play: "swift.play"
329342
]
330343
let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities))
331344
let toolchainRegistry = ToolchainRegistry(toolchains: [toolchainWithSwiftPlay])
@@ -369,7 +382,7 @@ final class CodeLensTests: XCTestCase {
369382
func testCodeLensRequestNoPlaygrounds() async throws {
370383
var codeLensCapabilities = TextDocumentClientCapabilities.CodeLens()
371384
codeLensCapabilities.supportedCommands = [
372-
SupportedCodeLensCommand.play: "swift.play",
385+
SupportedCodeLensCommand.play: "swift.play"
373386
]
374387
let capabilities = ClientCapabilities(textDocument: TextDocumentClientCapabilities(codeLens: codeLensCapabilities))
375388
let toolchainRegistry = ToolchainRegistry(toolchains: [toolchainWithSwiftPlay])

0 commit comments

Comments
 (0)