Skip to content

Commit d4b63a2

Browse files
add textDocument/convertDocumentation request to SourceKit-LSP
1 parent f1251c0 commit d4b63a2

File tree

11 files changed

+1124
-12
lines changed

11 files changed

+1124
-12
lines changed

Contributor Documentation/LSP Extensions.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,94 @@ interface SKCompletionOptions {
169169
}
170170
```
171171
172+
## `textDocument/convertDocumentation`
173+
174+
New request that returns a RenderNode for a symbol at a given location that can then be
175+
rendered in an editor by `swiftlang/swift-docc-render`.
176+
177+
This request uses Swift DocC's convert service to convert documentation for a Swift symbol
178+
or Markup/Tutorial file. It responds with a string containing a JSON encoded `RenderNode`
179+
or an error if the documentation could not be converted. This error message can be displayed
180+
to the user in the live preview editor.
181+
182+
At the moment this request is only available on macOS and Linux. If SourceKit-LSP supports
183+
this request it will add `textDocument/convertDocumentation` to its experimental server
184+
capabilities.
185+
186+
```ts
187+
export interface ConvertDocumentationParams {
188+
/**
189+
* The document to render documentation for.
190+
*/
191+
textDocument: TextDocumentIdentifier;
192+
193+
/**
194+
* The document location at which to lookup symbol information.
195+
*
196+
* This parameter is only used in Swift files to determine which symbol to render.
197+
* The position is ignored for markdown and tutorial documents.
198+
*/
199+
position: Position;
200+
}
201+
202+
export type ConvertDocumentationResponse = RenderNodeResponse | ErrorResponse;
203+
204+
interface RenderNodeResponse {
205+
/**
206+
* The type of this response: either a RenderNode or error.
207+
*/
208+
type: "renderNode";
209+
210+
/**
211+
* The JSON encoded RenderNode that can be rendered by swift-docc-render.
212+
*/
213+
renderNode: string;
214+
}
215+
216+
interface ErrorResponse {
217+
/**
218+
* The type of this response: either a RenderNode or error.
219+
*/
220+
type: "error";
221+
222+
/**
223+
* The error that occurred.
224+
*/
225+
error: ConvertDocumentationError;
226+
}
227+
228+
export type ConvertDocumentationError = ErrorWithNoParams | SymbolNotFoundError;
229+
230+
interface ErrorWithNoParams {
231+
/**
232+
* The kind of error that occurred.
233+
*/
234+
kind: "indexNotAvailable" | "noDocumentation";
235+
236+
/**
237+
* A human readable error message that can be shown to the user.
238+
*/
239+
message: string;
240+
}
241+
242+
interface SymbolNotFoundError {
243+
/**
244+
* The kind of error that occurred.
245+
*/
246+
kind: "symbolNotFound";
247+
248+
/**
249+
* The name of the symbol that could not be found.
250+
*/
251+
symbolName: string;
252+
253+
/**
254+
* A human readable error message that can be shown to the user.
255+
*/
256+
message: string;
257+
}
258+
```
259+
172260
## `textDocument/symbolInfo`
173261
174262
New request for semantic information about the symbol at a given location.

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ var targets: [Target] = [
473473
"SwiftExtensions",
474474
"ToolchainRegistry",
475475
"TSCExtensions",
476+
.product(name: "SwiftDocC", package: "swift-docc"),
476477
.product(name: "IndexStoreDB", package: "indexstore-db"),
477478
.product(name: "Crypto", package: "swift-crypto"),
478479
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
@@ -751,6 +752,7 @@ var dependencies: [Package.Dependency] {
751752
} else if useLocalDependencies {
752753
return [
753754
.package(path: "../indexstore-db"),
755+
.package(path: "../swift-docc"),
754756
.package(path: "../swift-tools-support-core"),
755757
.package(path: "../swift-argument-parser"),
756758
.package(path: "../swift-syntax"),
@@ -761,6 +763,7 @@ var dependencies: [Package.Dependency] {
761763

762764
return [
763765
.package(url: "https://github.com/swiftlang/indexstore-db.git", branch: relatedDependenciesBranch),
766+
.package(url: "https://github.com/swiftlang/swift-docc.git", branch: relatedDependenciesBranch),
764767
.package(url: "https://github.com/apple/swift-tools-support-core.git", branch: relatedDependenciesBranch),
765768
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"),
766769
.package(url: "https://github.com/swiftlang/swift-syntax.git", branch: relatedDependenciesBranch),

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ add_library(LanguageServerProtocol STATIC
3838
Requests/ColorPresentationRequest.swift
3939
Requests/CompletionItemResolveRequest.swift
4040
Requests/CompletionRequest.swift
41+
Requests/ConvertDocumentationRequest.swift
4142
Requests/CreateWorkDoneProgressRequest.swift
4243
Requests/DeclarationRequest.swift
4344
Requests/DefinitionRequest.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public let builtinRequests: [_RequestType.Type] = [
7070
ShowMessageRequest.self,
7171
ShutdownRequest.self,
7272
SignatureHelpRequest.self,
73+
ConvertDocumentationRequest.self,
7374
SymbolInfoRequest.self,
7475
TriggerReindexRequest.self,
7576
TypeDefinitionRequest.self,
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Request for converting documentation for the symbol at a given location **(LSP Extension)**.
14+
///
15+
/// This request looks up the symbol (if any) at a given text document location and returns a
16+
/// ``ConvertDocumentationResponse`` for that location. This request is primarily designed for editors
17+
/// to support live preview of Swift documentation.
18+
///
19+
/// - Parameters:
20+
/// - textDocument: The document to render documentation for.
21+
/// - position: The document location at which to lookup symbol information.
22+
///
23+
/// - Returns: A ``ConvertDocumentationResponse`` for the given location, which may contain an error
24+
/// message if documentation could not be converted. This error message can be displayed to the user
25+
/// in the live preview editor.
26+
///
27+
/// ### LSP Extension
28+
///
29+
/// This request is an extension to LSP supported by SourceKit-LSP.
30+
/// The client is expected to display the documentation in an editor using swift-docc-render.
31+
public struct ConvertDocumentationRequest: TextDocumentRequest, Hashable {
32+
public static let method: String = "textDocument/convertDocumentation"
33+
public typealias Response = ConvertDocumentationResponse
34+
35+
/// The document in which to lookup the symbol location.
36+
public var textDocument: TextDocumentIdentifier
37+
38+
/// The document location at which to lookup symbol information.
39+
public var position: Position
40+
41+
public init(textDocument: TextDocumentIdentifier, position: Position) {
42+
self.textDocument = textDocument
43+
self.position = position
44+
}
45+
}
46+
47+
public enum ConvertDocumentationResponse: ResponseType {
48+
case renderNode(String)
49+
case error(ConvertDocumentationError)
50+
}
51+
52+
public enum ConvertDocumentationError: ResponseType, Equatable {
53+
case indexNotAvailable
54+
case noDocumentation
55+
case symbolNotFound(String)
56+
57+
var message: String {
58+
switch self {
59+
case .indexNotAvailable:
60+
return "The index is not availble to complete the request"
61+
case .noDocumentation:
62+
return "No documentation could be rendered for the position in this document"
63+
case .symbolNotFound(let symbolName):
64+
return "Could not find symbol \(symbolName) in the project"
65+
}
66+
}
67+
}
68+
69+
extension ConvertDocumentationError: Codable {
70+
enum CodingKeys: String, CodingKey {
71+
case kind
72+
case message
73+
case symbolName
74+
}
75+
76+
public init(from decoder: Decoder) throws {
77+
let values = try decoder.container(keyedBy: CodingKeys.self)
78+
let kind = try values.decode(String.self, forKey: .kind)
79+
switch kind {
80+
case "indexNotAvailable":
81+
self = .indexNotAvailable
82+
case "noDocumentation":
83+
self = .noDocumentation
84+
case "symbolNotFound":
85+
let symbolName = try values.decode(String.self, forKey: .symbolName)
86+
self = .symbolNotFound(symbolName)
87+
default:
88+
throw DecodingError.dataCorruptedError(
89+
forKey: CodingKeys.kind,
90+
in: values,
91+
debugDescription: "Invalid error kind: \(kind)"
92+
)
93+
}
94+
}
95+
96+
public func encode(to encoder: any Encoder) throws {
97+
var container = encoder.container(keyedBy: CodingKeys.self)
98+
switch self {
99+
case .indexNotAvailable:
100+
try container.encode("indexNotAvailable", forKey: .kind)
101+
case .noDocumentation:
102+
try container.encode("noDocumentation", forKey: .kind)
103+
case .symbolNotFound(let symbolName):
104+
try container.encode("symbolNotFound", forKey: .kind)
105+
try container.encode(symbolName, forKey: .symbolName)
106+
}
107+
try container.encode(message, forKey: .message)
108+
}
109+
}
110+
111+
extension ConvertDocumentationResponse: Codable {
112+
enum CodingKeys: String, CodingKey {
113+
case type
114+
case renderNode
115+
case error
116+
}
117+
118+
public init(from decoder: Decoder) throws {
119+
let values = try decoder.container(keyedBy: CodingKeys.self)
120+
let type = try values.decode(String.self, forKey: .type)
121+
switch type {
122+
case "renderNode":
123+
let renderNode = try values.decode(String.self, forKey: .renderNode)
124+
self = .renderNode(renderNode)
125+
case "error":
126+
let error = try values.decode(ConvertDocumentationError.self, forKey: .error)
127+
self = .error(error)
128+
default:
129+
throw DecodingError.dataCorruptedError(
130+
forKey: CodingKeys.type,
131+
in: values,
132+
debugDescription: "Invalid type: \(type)"
133+
)
134+
}
135+
}
136+
137+
public func encode(to encoder: any Encoder) throws {
138+
var container = encoder.container(keyedBy: CodingKeys.self)
139+
switch self {
140+
case .renderNode(let renderNode):
141+
try container.encode("renderNode", forKey: .type)
142+
try container.encode(renderNode, forKey: .renderNode)
143+
case .error(let error):
144+
try container.encode("error", forKey: .type)
145+
try container.encode(error, forKey: .error)
146+
}
147+
}
148+
}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,12 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
238238
// MARK: - Sending messages
239239

240240
/// Send the request to `server` and return the request result.
241-
package func send<R: RequestType>(_ request: R) async throws -> R.Response {
242-
return try await withCheckedThrowingContinuation { continuation in
241+
package func send<R: RequestType>(_ request: R) async throws(ResponseError) -> R.Response {
242+
return try await withCheckedContinuation { continuation in
243243
self.send(request) { result in
244-
continuation.resume(with: result)
244+
continuation.resume(returning: result)
245245
}
246-
}
246+
}.get()
247247
}
248248

249249
/// Variant of `send` above that allows the response to be discarded if it is a `VoidResponse`.

0 commit comments

Comments
 (0)