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

Migrate to upstream LSP inlay hints request #465

Merged
merged 1 commit into from
Jun 14, 2022
Merged
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
2 changes: 1 addition & 1 deletion Sources/LanguageServerProtocol/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ add_library(LanguageServerProtocol STATIC
Requests/HoverRequest.swift
Requests/ImplementationRequest.swift
Requests/InitializeRequest.swift
Requests/InlayHintsRequest.swift
Requests/InlayHintRequest.swift
Requests/PollIndexRequest.swift
Requests/PrepareRenameRequest.swift
Requests/ReferencesRequest.swift
Expand Down
2 changes: 1 addition & 1 deletion Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ public let builtinRequests: [_RequestType.Type] = [
RenameRequest.self,
RegisterCapabilityRequest.self,
UnregisterCapabilityRequest.self,
InlayHintRequest.self,

// MARK: LSP Extension Requests

SymbolInfoRequest.self,
PollIndexRequest.self,
InlayHintsRequest.self,
]

/// The set of known notifications.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
//
//===----------------------------------------------------------------------===//

/// Request for inline annotations to be displayed in the editor **(LSP Extension)**.
/// Request for inline annotations to be displayed in the editor.
///
/// This implements the proposed `textDocument/inlayHints` API from
/// This implements the proposed `textDocument/inlayHint` API from
/// https://github.com/microsoft/language-server-protocol/pull/1249 (commit: `d55733d`)
///
/// - Parameters:
/// - textDocument: The document for which to provide the inlay hints.
///
/// - Returns: InlayHints for the entire document
public struct InlayHintsRequest: TextDocumentRequest, Hashable {
public static let method: String = "sourcekit-lsp/inlayHints"
public struct InlayHintRequest: TextDocumentRequest, Hashable {
public static let method: String = "textDocument/inlayHint"
public typealias Response = [InlayHint]

/// The document for which to provide the inlay hints.
Expand All @@ -33,12 +33,12 @@ public struct InlayHintsRequest: TextDocumentRequest, Hashable {

/// The categories of hints that are interesting to the client
/// and should be filtered.
public var only: [InlayHintCategory]?
public var only: [InlayHintKind]?

public init(
textDocument: TextDocumentIdentifier,
range: Range<Position>? = nil,
only: [InlayHintCategory]? = nil
only: [InlayHintKind]? = nil
) {
self.textDocument = textDocument
self._range = CustomCodable(wrappedValue: range)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,33 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
}
}

/// Capabilities specific to 'textDocument/inlayHint'.
public struct InlayHint: Hashable, Codable {
/// Properties a client can resolve lazily.
public struct ResolveSupport: Hashable, Codable {
/// The properties that a client can resolve lazily.
public var properties: [String]

public init(properties: [String] = []) {
self.properties = properties
}
}

/// Whether inlay hints support dynamic registration.
public var dynamicRegistration: Bool?

/// Indicates which properties a client can resolve lazily on an inlay hint.
public var resolveSupport: ResolveSupport?

public init(
dynamicRegistration: Bool? = nil,
resolveSupport: ResolveSupport? = nil
) {
self.dynamicRegistration = dynamicRegistration
self.resolveSupport = resolveSupport
}
}

// MARK: Properties

public var synchronization: Synchronization? = nil
Expand Down Expand Up @@ -547,6 +574,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {

public var semanticTokens: SemanticTokens? = nil

public var inlayHint: InlayHint? = nil

public init(synchronization: Synchronization? = nil,
completion: Completion? = nil,
hover: Hover? = nil,
Expand All @@ -569,7 +598,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
publishDiagnostics: PublishDiagnostics? = nil,
foldingRange: FoldingRange? = nil,
callHierarchy: DynamicRegistrationCapability? = nil,
semanticTokens: SemanticTokens? = nil) {
semanticTokens: SemanticTokens? = nil,
inlayHint: InlayHint? = nil) {
self.synchronization = synchronization
self.completion = completion
self.hover = hover
Expand All @@ -593,5 +623,6 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
self.foldingRange = foldingRange
self.callHierarchy = callHierarchy
self.semanticTokens = semanticTokens
self.inlayHint = inlayHint
}
}
115 changes: 104 additions & 11 deletions Sources/LanguageServerProtocol/SupportTypes/InlayHint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,126 @@ public struct InlayHint: ResponseType, Codable, Hashable {
public var position: Position

/// The hint's kind, used for more flexible client-side styling.
public let category: InlayHintCategory?
public let kind: InlayHintKind?

/// The hint's text, e.g. a printed type
public let label: String
public let label: InlayHintLabel

/// Optional text edits that are performed when accepting this inlay hint.
public let textEdits: [TextEdit]?

/// The tooltip text displayed when the inlay hint is hovered.
public let tooltip: MarkupContent?

/// Whether to render padding before the hint.
public let paddingLeft: Bool?

/// Whether to render padding after the hint.
public let paddingRight: Bool?

fwcd marked this conversation as resolved.
Show resolved Hide resolved
/// A data entry field that is present between a `textDocument/inlayHint`
/// and a `inlayHint/resolve` request.
public let data: LSPAny?

public init(
position: Position,
category: InlayHintCategory? = nil,
label: String
kind: InlayHintKind? = nil,
label: InlayHintLabel,
textEdits: [TextEdit]? = nil,
tooltip: MarkupContent? = nil,
paddingLeft: Bool? = nil,
paddingRight: Bool? = nil,
data: LSPAny? = nil
) {
self.position = position
self.category = category
self.kind = kind
self.label = label
self.textEdits = textEdits
self.tooltip = tooltip
self.paddingLeft = paddingLeft
self.paddingRight = paddingRight
self.data = data
}
}

/// A hint's kind, used for more flexible client-side styling.
public struct InlayHintCategory: RawRepresentable, Codable, Hashable {
public var rawValue: String
public struct InlayHintKind: RawRepresentable, Codable, Hashable {
public var rawValue: Int

public init(rawValue: String) {
public init(rawValue: Int) {
self.rawValue = rawValue
}

/// A type annotation.
public static let type: InlayHintKind = InlayHintKind(rawValue: 1)
/// A parameter label. Note that this case is not used by
/// Swift, since Swift already has explicit parameter labels.
public static let parameter: InlayHintCategory = InlayHintCategory(rawValue: "parameter")
/// An inferred type.
public static let type: InlayHintCategory = InlayHintCategory(rawValue: "type")
public static let parameter: InlayHintKind = InlayHintKind(rawValue: 2)
}

/// A hint's label, either being a single string or a composition of parts.
public enum InlayHintLabel: Codable, Hashable {
case parts([InlayHintLabelPart])
case string(String)

public init(from decoder: Decoder) throws {
if let parts = try? [InlayHintLabelPart](from: decoder) {
self = .parts(parts)
} else if let string = try? String(from: decoder) {
self = .string(string)
} else {
let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected [InlayHintLabelPart] or String")
throw DecodingError.dataCorrupted(context)
}
}

public func encode(to encoder: Encoder) throws {
switch self {
case let .parts(parts):
try parts.encode(to: encoder)
case let .string(string):
try string.encode(to: encoder)
}
}
}

extension InlayHintLabel: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self = .string(value)
}
}

extension InlayHintLabel: ExpressibleByStringInterpolation {
public init(stringInterpolation interpolation: DefaultStringInterpolation) {
self = .string(.init(stringInterpolation: interpolation))
}
}

/// A part of an interactive or composite inlay hint label.
public struct InlayHintLabelPart: Codable, Hashable {
/// The value of this label part.
public let value: String

/// The tooltip to show when the part is hovered.
public let tooltip: MarkupContent?

/// An optional source code location representing this part.
/// Used by the editor for hover and code navigation, e.g.
/// by making the part a clickable link to the given position.
public let location: Location?

/// An optional command for this label part.
public let command: Command?

public init(
value: String,
tooltip: MarkupContent? = nil,
location: Location? = nil,
command: Command? = nil
) {
self.value = value
self.tooltip = tooltip
self.location = location
self.command = command
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,26 @@ public struct SemanticTokensRegistrationOptions: RegistrationOptions, TextDocume
}
}

public struct InlayHintRegistrationOptions: RegistrationOptions, TextDocumentRegistrationOptionsProtocol, Hashable {
public var textDocumentRegistrationOptions: TextDocumentRegistrationOptions
public var inlayHintOptions: InlayHintOptions

public init(
documentSelector: DocumentSelector? = nil,
inlayHintOptions: InlayHintOptions
) {
textDocumentRegistrationOptions = TextDocumentRegistrationOptions(documentSelector: documentSelector)
self.inlayHintOptions = inlayHintOptions
}

public func encodeIntoLSPAny(dict: inout [String: LSPAny]) {
textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict)
if let resolveProvider = inlayHintOptions.resolveProvider {
dict["resolveProvider"] = .bool(resolveProvider)
}
}
}

/// Describe options to be used when registering for file system change events.
public struct DidChangeWatchedFilesRegistrationOptions: RegistrationOptions {
/// The watchers to register.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ public struct ServerCapabilities: Codable, Hashable {
/// requests.
public var semanticTokensProvider: SemanticTokensOptions?

/// Whether the server supports the `textDocument/inlayHint` family of requests.
public var inlayHintProvider: InlayHintOptions?

public var experimental: LSPAny?

public init(
Expand Down Expand Up @@ -117,6 +120,7 @@ public struct ServerCapabilities: Codable, Hashable {
workspace: WorkspaceServerCapabilities? = nil,
callHierarchyProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
semanticTokensProvider: SemanticTokensOptions? = nil,
inlayHintProvider: InlayHintOptions? = nil,
experimental: LSPAny? = nil
)
{
Expand Down Expand Up @@ -145,6 +149,7 @@ public struct ServerCapabilities: Codable, Hashable {
self.workspace = workspace
self.callHierarchyProvider = callHierarchyProvider
self.semanticTokensProvider = semanticTokensProvider
self.inlayHintProvider = inlayHintProvider
self.experimental = experimental
}
}
Expand Down Expand Up @@ -505,6 +510,16 @@ public struct SemanticTokensOptions: Codable, Hashable {
}
}

public struct InlayHintOptions: Codable, Hashable {
/// The server provides support to resolve additional information
/// for an inlay hint item.
public var resolveProvider: Bool?

public init(resolveProvider: Bool? = nil) {
self.resolveProvider = resolveProvider
}
}

public struct WorkspaceServerCapabilities: Codable, Hashable {
public struct WorkspaceFolders: Codable, Hashable {
/// The server has support for workspace folders
Expand Down
35 changes: 35 additions & 0 deletions Sources/SourceKitLSP/CapabilityRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public final class CapabilityRegistry {
/// Dynamically registered semantic tokens options.
private var semanticTokens: [CapabilityRegistration: SemanticTokensRegistrationOptions] = [:]

/// Dynamically registered inlay hint options.
private var inlayHint: [CapabilityRegistration: InlayHintRegistrationOptions] = [:]

/// Dynamically registered file watchers.
private var didChangeWatchedFiles: DidChangeWatchedFilesRegistrationOptions?

Expand All @@ -53,6 +56,10 @@ public final class CapabilityRegistry {
clientCapabilities.textDocument?.semanticTokens?.dynamicRegistration == true
}

public var clientHasDynamicInlayHintRegistration: Bool {
clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true
}

public var clientHasDynamicExecuteCommandRegistration: Bool {
clientCapabilities.workspace?.executeCommand?.dynamicRegistration == true
}
Expand Down Expand Up @@ -167,6 +174,34 @@ public final class CapabilityRegistry {
registerOnClient(registration)
}

/// Dynamically register inlay hint capabilities if the client supports
/// it and we haven't yet registered any inlay hint capabilities for the
/// given languages.
public func registerInlayHintIfNeeded(
options: InlayHintOptions,
for languages: [Language],
registerOnClient: ClientRegistrationHandler
) {
guard clientHasDynamicInlayHintRegistration else { return }
if let registration = registration(for: languages, in: inlayHint) {
if options != registration.inlayHintOptions {
log("Unable to register new inlay hint options \(options) for " +
"\(languages) due to pre-existing options \(registration.inlayHintOptions)", level: .warning)
}
return
}
let registrationOptions = InlayHintRegistrationOptions(
documentSelector: self.documentSelector(for: languages),
inlayHintOptions: options)
let registration = CapabilityRegistration(
method: InlayHintRequest.method,
registerOptions: self.encode(registrationOptions))

self.inlayHint[registration] = registrationOptions

registerOnClient(registration)
}

/// Dynamically register executeCommand with the given IDs if the client supports
/// it and we haven't yet registered the given command IDs yet.
public func registerExecuteCommandIfNeeded(
Expand Down
7 changes: 2 additions & 5 deletions Sources/SourceKitLSP/Clang/ClangLanguageServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,8 @@ extension ClangLanguageServerShim {
forwardRequestToClangdOnQueue(req)
}

func inlayHints(_ req: Request<InlayHintsRequest>) {
// FIXME: Currently a Swift-specific, non-standard request.
// Once inlay hints have been upstreamed to LSP, forward
// them to clangd.
req.reply(.success([]))
func inlayHint(_ req: Request<InlayHintRequest>) {
forwardRequestToClangdOnQueue(req)
}

func foldingRange(_ req: Request<FoldingRangeRequest>) {
Expand Down
Loading