Skip to content

Commit 818c44d

Browse files
committed
Migrate to upstream LSP inlay hints
- Use official textDocument/inlayHint request - Rename InlayHintCategory to InlayHintKind - Additionally, represent it using an Int, as in the proposed LSP API. - Add inlay hint client capabilities - Add inlay hint server capabilities - Add dynamic registration of inlay hint request - Rename InlayHintsRequest -> InlayHintRequest This is to be consistent with the request itself being named in singular in LSP and the other requests (e.g. DocumentSymbolRequest). - Forward inlay hint requests to clangd - Add colon before inlay hints - Add other properties to InlayHint - Add InlayHintLabel structures - Conform InlayHintLabel to ExpressibleByStringX protocols - Attach TextEdit to inlay hints for committing them - Add InlayHint.data - Fix InlayHintTests We need to include text edits in the expected inlay hints.
1 parent 6fa0771 commit 818c44d

13 files changed

+299
-85
lines changed

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ add_library(LanguageServerProtocol STATIC
4141
Requests/HoverRequest.swift
4242
Requests/ImplementationRequest.swift
4343
Requests/InitializeRequest.swift
44-
Requests/InlayHintsRequest.swift
44+
Requests/InlayHintRequest.swift
4545
Requests/PollIndexRequest.swift
4646
Requests/PrepareRenameRequest.swift
4747
Requests/ReferencesRequest.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public let builtinRequests: [_RequestType.Type] = [
4747
RenameRequest.self,
4848
RegisterCapabilityRequest.self,
4949
UnregisterCapabilityRequest.self,
50+
InlayHintRequest.self,
5051

5152
// MARK: LSP Extension Requests
5253

5354
SymbolInfoRequest.self,
5455
PollIndexRequest.self,
55-
InlayHintsRequest.self,
5656
]
5757

5858
/// The set of known notifications.

Sources/LanguageServerProtocol/Requests/InlayHintsRequest.swift renamed to Sources/LanguageServerProtocol/Requests/InlayHintRequest.swift

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

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

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

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

3838
public init(
3939
textDocument: TextDocumentIdentifier,
4040
range: Range<Position>? = nil,
41-
only: [InlayHintCategory]? = nil
41+
only: [InlayHintKind]? = nil
4242
) {
4343
self.textDocument = textDocument
4444
self._range = CustomCodable(wrappedValue: range)

Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,33 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
499499
}
500500
}
501501

502+
/// Capabilities specific to 'textDocument/inlayHint'.
503+
public struct InlayHint: Hashable, Codable {
504+
/// Properties a client can resolve lazily.
505+
public struct ResolveSupport: Hashable, Codable {
506+
/// The properties that a client can resolve lazily.
507+
public var properties: [String]
508+
509+
public init(properties: [String] = []) {
510+
self.properties = properties
511+
}
512+
}
513+
514+
/// Whether inlay hints support dynamic registration.
515+
public var dynamicRegistration: Bool?
516+
517+
/// Indicates which properties a client can resolve lazily on an inlay hint.
518+
public var resolveSupport: ResolveSupport?
519+
520+
public init(
521+
dynamicRegistration: Bool? = nil,
522+
resolveSupport: ResolveSupport? = nil
523+
) {
524+
self.dynamicRegistration = dynamicRegistration
525+
self.resolveSupport = resolveSupport
526+
}
527+
}
528+
502529
// MARK: Properties
503530

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

548575
public var semanticTokens: SemanticTokens? = nil
549576

577+
public var inlayHint: InlayHint? = nil
578+
550579
public init(synchronization: Synchronization? = nil,
551580
completion: Completion? = nil,
552581
hover: Hover? = nil,
@@ -569,7 +598,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
569598
publishDiagnostics: PublishDiagnostics? = nil,
570599
foldingRange: FoldingRange? = nil,
571600
callHierarchy: DynamicRegistrationCapability? = nil,
572-
semanticTokens: SemanticTokens? = nil) {
601+
semanticTokens: SemanticTokens? = nil,
602+
inlayHint: InlayHint? = nil) {
573603
self.synchronization = synchronization
574604
self.completion = completion
575605
self.hover = hover
@@ -593,5 +623,6 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
593623
self.foldingRange = foldingRange
594624
self.callHierarchy = callHierarchy
595625
self.semanticTokens = semanticTokens
626+
self.inlayHint = inlayHint
596627
}
597628
}

Sources/LanguageServerProtocol/SupportTypes/InlayHint.swift

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,126 @@ public struct InlayHint: ResponseType, Codable, Hashable {
1616
public var position: Position
1717

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

2121
/// The hint's text, e.g. a printed type
22-
public let label: String
22+
public let label: InlayHintLabel
23+
24+
/// Optional text edits that are performed when accepting this inlay hint.
25+
public let textEdits: [TextEdit]?
26+
27+
/// The tooltip text displayed when the inlay hint is hovered.
28+
public let tooltip: MarkupContent?
29+
30+
/// Whether to render padding before the hint.
31+
public let paddingLeft: Bool?
32+
33+
/// Whether to render padding after the hint.
34+
public let paddingRight: Bool?
35+
36+
/// A data entry field that is present between a `textDocument/inlayHint`
37+
/// and a `inlayHint/resolve` request.
38+
public let data: LSPAny?
2339

2440
public init(
2541
position: Position,
26-
category: InlayHintCategory? = nil,
27-
label: String
42+
kind: InlayHintKind? = nil,
43+
label: InlayHintLabel,
44+
textEdits: [TextEdit]? = nil,
45+
tooltip: MarkupContent? = nil,
46+
paddingLeft: Bool? = nil,
47+
paddingRight: Bool? = nil,
48+
data: LSPAny? = nil
2849
) {
2950
self.position = position
30-
self.category = category
51+
self.kind = kind
3152
self.label = label
53+
self.textEdits = textEdits
54+
self.tooltip = tooltip
55+
self.paddingLeft = paddingLeft
56+
self.paddingRight = paddingRight
57+
self.data = data
3258
}
3359
}
3460

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

39-
public init(rawValue: String) {
65+
public init(rawValue: Int) {
4066
self.rawValue = rawValue
4167
}
4268

69+
/// A type annotation.
70+
public static let type: InlayHintKind = InlayHintKind(rawValue: 1)
4371
/// A parameter label. Note that this case is not used by
4472
/// Swift, since Swift already has explicit parameter labels.
45-
public static let parameter: InlayHintCategory = InlayHintCategory(rawValue: "parameter")
46-
/// An inferred type.
47-
public static let type: InlayHintCategory = InlayHintCategory(rawValue: "type")
73+
public static let parameter: InlayHintKind = InlayHintKind(rawValue: 2)
74+
}
75+
76+
/// A hint's label, either being a single string or a composition of parts.
77+
public enum InlayHintLabel: Codable, Hashable {
78+
case parts([InlayHintLabelPart])
79+
case string(String)
80+
81+
public init(from decoder: Decoder) throws {
82+
if let parts = try? [InlayHintLabelPart](from: decoder) {
83+
self = .parts(parts)
84+
} else if let string = try? String(from: decoder) {
85+
self = .string(string)
86+
} else {
87+
let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected [InlayHintLabelPart] or String")
88+
throw DecodingError.dataCorrupted(context)
89+
}
90+
}
91+
92+
public func encode(to encoder: Encoder) throws {
93+
switch self {
94+
case let .parts(parts):
95+
try parts.encode(to: encoder)
96+
case let .string(string):
97+
try string.encode(to: encoder)
98+
}
99+
}
100+
}
101+
102+
extension InlayHintLabel: ExpressibleByStringLiteral {
103+
public init(stringLiteral value: String) {
104+
self = .string(value)
105+
}
106+
}
107+
108+
extension InlayHintLabel: ExpressibleByStringInterpolation {
109+
public init(stringInterpolation interpolation: DefaultStringInterpolation) {
110+
self = .string(.init(stringInterpolation: interpolation))
111+
}
112+
}
113+
114+
/// A part of an interactive or composite inlay hint label.
115+
public struct InlayHintLabelPart: Codable, Hashable {
116+
/// The value of this label part.
117+
public let value: String
118+
119+
/// The tooltip to show when the part is hovered.
120+
public let tooltip: MarkupContent?
121+
122+
/// An optional source code location representing this part.
123+
/// Used by the editor for hover and code navigation, e.g.
124+
/// by making the part a clickable link to the given position.
125+
public let location: Location?
126+
127+
/// An optional command for this label part.
128+
public let command: Command?
129+
130+
public init(
131+
value: String,
132+
tooltip: MarkupContent? = nil,
133+
location: Location? = nil,
134+
command: Command? = nil
135+
) {
136+
self.value = value
137+
self.tooltip = tooltip
138+
self.location = location
139+
self.command = command
140+
}
48141
}

Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ public struct SemanticTokensRegistrationOptions: RegistrationOptions, TextDocume
136136
}
137137
}
138138

139+
public struct InlayHintRegistrationOptions: RegistrationOptions, TextDocumentRegistrationOptionsProtocol, Hashable {
140+
public var textDocumentRegistrationOptions: TextDocumentRegistrationOptions
141+
public var inlayHintOptions: InlayHintOptions
142+
143+
public init(
144+
documentSelector: DocumentSelector? = nil,
145+
inlayHintOptions: InlayHintOptions
146+
) {
147+
textDocumentRegistrationOptions = TextDocumentRegistrationOptions(documentSelector: documentSelector)
148+
self.inlayHintOptions = inlayHintOptions
149+
}
150+
151+
public func encodeIntoLSPAny(dict: inout [String: LSPAny]) {
152+
textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict)
153+
if let resolveProvider = inlayHintOptions.resolveProvider {
154+
dict["resolveProvider"] = .bool(resolveProvider)
155+
}
156+
}
157+
}
158+
139159
/// Describe options to be used when registering for file system change events.
140160
public struct DidChangeWatchedFilesRegistrationOptions: RegistrationOptions {
141161
/// The watchers to register.

Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ public struct ServerCapabilities: Codable, Hashable {
8989
/// requests.
9090
public var semanticTokensProvider: SemanticTokensOptions?
9191

92+
/// Whether the server supports the `textDocument/inlayHint` family of requests.
93+
public var inlayHintProvider: InlayHintOptions?
94+
9295
public var experimental: LSPAny?
9396

9497
public init(
@@ -117,6 +120,7 @@ public struct ServerCapabilities: Codable, Hashable {
117120
workspace: WorkspaceServerCapabilities? = nil,
118121
callHierarchyProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
119122
semanticTokensProvider: SemanticTokensOptions? = nil,
123+
inlayHintProvider: InlayHintOptions? = nil,
120124
experimental: LSPAny? = nil
121125
)
122126
{
@@ -145,6 +149,7 @@ public struct ServerCapabilities: Codable, Hashable {
145149
self.workspace = workspace
146150
self.callHierarchyProvider = callHierarchyProvider
147151
self.semanticTokensProvider = semanticTokensProvider
152+
self.inlayHintProvider = inlayHintProvider
148153
self.experimental = experimental
149154
}
150155
}
@@ -505,6 +510,16 @@ public struct SemanticTokensOptions: Codable, Hashable {
505510
}
506511
}
507512

513+
public struct InlayHintOptions: Codable, Hashable {
514+
/// The server provides support to resolve additional information
515+
/// for an inlay hint item.
516+
public var resolveProvider: Bool?
517+
518+
public init(resolveProvider: Bool? = nil) {
519+
self.resolveProvider = resolveProvider
520+
}
521+
}
522+
508523
public struct WorkspaceServerCapabilities: Codable, Hashable {
509524
public struct WorkspaceFolders: Codable, Hashable {
510525
/// The server has support for workspace folders

Sources/SourceKitLSP/CapabilityRegistry.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ public final class CapabilityRegistry {
2929
/// Dynamically registered semantic tokens options.
3030
private var semanticTokens: [CapabilityRegistration: SemanticTokensRegistrationOptions] = [:]
3131

32+
/// Dynamically registered inlay hint options.
33+
private var inlayHint: [CapabilityRegistration: InlayHintRegistrationOptions] = [:]
34+
3235
/// Dynamically registered file watchers.
3336
private var didChangeWatchedFiles: DidChangeWatchedFilesRegistrationOptions?
3437

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

59+
public var clientHasDynamicInlayHintRegistration: Bool {
60+
clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true
61+
}
62+
5663
public var clientHasDynamicExecuteCommandRegistration: Bool {
5764
clientCapabilities.workspace?.executeCommand?.dynamicRegistration == true
5865
}
@@ -167,6 +174,34 @@ public final class CapabilityRegistry {
167174
registerOnClient(registration)
168175
}
169176

177+
/// Dynamically register inlay hint capabilities if the client supports
178+
/// it and we haven't yet registered any inlay hint capabilities for the
179+
/// given languages.
180+
public func registerInlayHintIfNeeded(
181+
options: InlayHintOptions,
182+
for languages: [Language],
183+
registerOnClient: ClientRegistrationHandler
184+
) {
185+
guard clientHasDynamicInlayHintRegistration else { return }
186+
if let registration = registration(for: languages, in: inlayHint) {
187+
if options != registration.inlayHintOptions {
188+
log("Unable to register new inlay hint options \(options) for " +
189+
"\(languages) due to pre-existing options \(registration.inlayHintOptions)", level: .warning)
190+
}
191+
return
192+
}
193+
let registrationOptions = InlayHintRegistrationOptions(
194+
documentSelector: self.documentSelector(for: languages),
195+
inlayHintOptions: options)
196+
let registration = CapabilityRegistration(
197+
method: InlayHintRequest.method,
198+
registerOptions: self.encode(registrationOptions))
199+
200+
self.inlayHint[registration] = registrationOptions
201+
202+
registerOnClient(registration)
203+
}
204+
170205
/// Dynamically register executeCommand with the given IDs if the client supports
171206
/// it and we haven't yet registered the given command IDs yet.
172207
public func registerExecuteCommandIfNeeded(

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,8 @@ extension ClangLanguageServerShim {
493493
forwardRequestToClangdOnQueue(req)
494494
}
495495

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

503500
func foldingRange(_ req: Request<FoldingRangeRequest>) {

0 commit comments

Comments
 (0)