Skip to content

Commit

Permalink
add textDocument/convertDocumentation request to SourceKit-LSP
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewbastien committed Dec 12, 2024
1 parent e0901f0 commit 88e5500
Show file tree
Hide file tree
Showing 17 changed files with 2,064 additions and 13 deletions.
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
9 changes: 9 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ var targets: [Target] = [
"SwiftExtensions",
"ToolchainRegistry",
"TSCExtensions",
.product(name: "SwiftDocC", package: "swift-docc"),
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
.product(name: "Markdown", package: "swift-markdown"),
.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 +573,9 @@ var dependencies: [Package.Dependency] {
} else if useLocalDependencies {
return [
.package(path: "../indexstore-db"),
.package(path: "../swift-docc"),
.package(path: "../swift-docc-symbolkit"),
.package(path: "../swift-markdown"),
.package(name: "swift-package-manager", path: "../swiftpm"),
.package(path: "../swift-tools-support-core"),
.package(path: "../swift-argument-parser"),
Expand All @@ -581,6 +587,9 @@ 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-docc-symbolkit.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/swiftlang/swift-markdown.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)
}
}
}
8 changes: 4 additions & 4 deletions Sources/SKTestSupport/TestSourceKitLSPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,12 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
// MARK: - Sending messages

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

/// Variant of `send` above that allows the response to be discarded if it is a `VoidResponse`.
Expand Down
12 changes: 12 additions & 0 deletions Sources/SemanticIndex/CheckedIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ package final class CheckedIndex {
}
}

@discardableResult package func forEachCanonicalSymbolOccurrence(
byName name: String,
body: (SymbolOccurrence) -> Bool
) -> Bool {
index.forEachCanonicalSymbolOccurrence(byName: name) { occurrence in
guard self.checker.isUpToDate(occurrence.location) else {
return true // continue
}
return body(occurrence)
}
}

package func symbols(inFilePath path: String) -> [Symbol] {
guard self.hasUpToDateUnit(for: DocumentURI(filePath: path, isDirectory: false)) else {
return []
Expand Down
Loading

0 comments on commit 88e5500

Please sign in to comment.