Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add textDocument/convertDocumentation request #1851

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions Contributor Documentation/LSP Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,94 @@ interface SKCompletionOptions {
}
```

## `textDocument/convertDocumentation`

New request that returns a RenderNode for a symbol at a given location that can then be
rendered in an editor by `swiftlang/swift-docc-render`.

This request uses Swift DocC's convert service to convert documentation for a Swift symbol
or Markup/Tutorial file. It responds with a string containing a JSON encoded `RenderNode`
or an error if the documentation could not be converted. This error message can be displayed
to the user in the live preview editor.

At the moment this request is only available on macOS and Linux. If SourceKit-LSP supports
this request it will add `textDocument/convertDocumentation` to its experimental server
capabilities.

```ts
export interface ConvertDocumentationParams {
/**
* The document to render documentation for.
*/
textDocument: TextDocumentIdentifier;

/**
* The document location at which to lookup symbol information.
*
* This parameter is only used in Swift files to determine which symbol to render.
* The position is ignored for markdown and tutorial documents.
*/
position: Position;
}

export type ConvertDocumentationResponse = RenderNodeResponse | ErrorResponse;

interface RenderNodeResponse {
/**
* The type of this response: either a RenderNode or error.
*/
type: "renderNode";

/**
* The JSON encoded RenderNode that can be rendered by swift-docc-render.
*/
renderNode: string;
}

interface ErrorResponse {
/**
* The type of this response: either a RenderNode or error.
*/
type: "error";

/**
* The error that occurred.
*/
error: ConvertDocumentationError;
}

export type ConvertDocumentationError = ErrorWithNoParams | SymbolNotFoundError;

interface ErrorWithNoParams {
/**
* The kind of error that occurred.
*/
kind: "indexNotAvailable" | "noDocumentation";

/**
* A human readable error message that can be shown to the user.
*/
message: string;
}

interface SymbolNotFoundError {
/**
* The kind of error that occurred.
*/
kind: "symbolNotFound";

/**
* The name of the symbol that could not be found.
*/
symbolName: string;

/**
* A human readable error message that can be shown to the user.
*/
message: string;
}
```

## `textDocument/symbolInfo`

New request for semantic information about the symbol at a given location.
Expand Down
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ var targets: [Target] = [
"SwiftExtensions",
"ToolchainRegistry",
"TSCExtensions",
.product(name: "SwiftDocC", package: "swift-docc"),
.product(name: "IndexStoreDB", package: "indexstore-db"),
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
Expand Down Expand Up @@ -570,6 +571,7 @@ var dependencies: [Package.Dependency] {
} else if useLocalDependencies {
return [
.package(path: "../indexstore-db"),
.package(path: "../swift-docc"),
.package(name: "swift-package-manager", path: "../swiftpm"),
.package(path: "../swift-tools-support-core"),
.package(path: "../swift-argument-parser"),
Expand All @@ -581,6 +583,7 @@ var dependencies: [Package.Dependency] {

return [
.package(url: "https://github.com/swiftlang/indexstore-db.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/swiftlang/swift-docc.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/swiftlang/swift-package-manager.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/apple/swift-tools-support-core.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"),
Expand Down
1 change: 1 addition & 0 deletions Sources/LanguageServerProtocol/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ add_library(LanguageServerProtocol STATIC
Requests/ColorPresentationRequest.swift
Requests/CompletionItemResolveRequest.swift
Requests/CompletionRequest.swift
Requests/ConvertDocumentationRequest.swift
Requests/CreateWorkDoneProgressRequest.swift
Requests/DeclarationRequest.swift
Requests/DefinitionRequest.swift
Expand Down
1 change: 1 addition & 0 deletions Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public let builtinRequests: [_RequestType.Type] = [
ShowMessageRequest.self,
ShutdownRequest.self,
SignatureHelpRequest.self,
ConvertDocumentationRequest.self,
SymbolInfoRequest.self,
TriggerReindexRequest.self,
TypeDefinitionRequest.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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
//
//===----------------------------------------------------------------------===//

/// Request for converting documentation for the symbol at a given location **(LSP Extension)**.
///
/// This request looks up the symbol (if any) at a given text document location and returns a
/// ``ConvertDocumentationResponse`` for that location. This request is primarily designed for editors
/// to support live preview of Swift documentation.
///
/// - Parameters:
/// - textDocument: The document to render documentation for.
/// - position: The document location at which to lookup symbol information.
///
/// - Returns: A ``ConvertDocumentationResponse`` for the given location, which may contain an error
/// message if documentation could not be converted. This error message can be displayed to the user
/// in the live preview editor.
///
/// ### LSP Extension
///
/// This request is an extension to LSP supported by SourceKit-LSP.
/// The client is expected to display the documentation in an editor using swift-docc-render.
public struct ConvertDocumentationRequest: TextDocumentRequest, Hashable {
public static let method: String = "textDocument/convertDocumentation"
public typealias Response = ConvertDocumentationResponse

/// The document in which to lookup the symbol location.
public var textDocument: TextDocumentIdentifier

/// The document location at which to lookup symbol information.
public var position: Position

public init(textDocument: TextDocumentIdentifier, position: Position) {
self.textDocument = textDocument
self.position = position
}
}

public enum ConvertDocumentationResponse: ResponseType {
case renderNode(String)
case error(ConvertDocumentationError)
}

public enum ConvertDocumentationError: ResponseType, Equatable {
case indexNotAvailable
case noDocumentation
case symbolNotFound(String)

var message: String {
switch self {
case .indexNotAvailable:
return "The index is not availble to complete the request"
case .noDocumentation:
return "No documentation could be rendered for the position in this document"
case .symbolNotFound(let symbolName):
return "Could not find symbol \(symbolName) in the project"
}
}
}

extension ConvertDocumentationError: Codable {
enum CodingKeys: String, CodingKey {
case kind
case message
case symbolName
}

public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let kind = try values.decode(String.self, forKey: .kind)
switch kind {
case "indexNotAvailable":
self = .indexNotAvailable
case "noDocumentation":
self = .noDocumentation
case "symbolNotFound":
let symbolName = try values.decode(String.self, forKey: .symbolName)
self = .symbolNotFound(symbolName)
default:
throw DecodingError.dataCorruptedError(
forKey: CodingKeys.kind,
in: values,
debugDescription: "Invalid error kind: \(kind)"
)
}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .indexNotAvailable:
try container.encode("indexNotAvailable", forKey: .kind)
case .noDocumentation:
try container.encode("noDocumentation", forKey: .kind)
case .symbolNotFound(let symbolName):
try container.encode("symbolNotFound", forKey: .kind)
try container.encode(symbolName, forKey: .symbolName)
}
try container.encode(message, forKey: .message)
}
}

extension ConvertDocumentationResponse: Codable {
enum CodingKeys: String, CodingKey {
case type
case renderNode
case error
}

public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let type = try values.decode(String.self, forKey: .type)
switch type {
case "renderNode":
let renderNode = try values.decode(String.self, forKey: .renderNode)
self = .renderNode(renderNode)
case "error":
let error = try values.decode(ConvertDocumentationError.self, forKey: .error)
self = .error(error)
default:
throw DecodingError.dataCorruptedError(
forKey: CodingKeys.type,
in: values,
debugDescription: "Invalid type: \(type)"
)
}
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .renderNode(let renderNode):
try container.encode("renderNode", forKey: .type)
try container.encode(renderNode, forKey: .renderNode)
case .error(let error):
try container.encode("error", forKey: .type)
try container.encode(error, forKey: .error)
}
}
}
Loading