Skip to content

Add boilerplate for pull-model diagnostics #743

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

Merged
merged 1 commit into from
May 4, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,16 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
}
}

/// Capabilities specific to 'textDocument/diagnostic'. Since LSP 3.17.0.
public struct Diagnostic: Equatable, Hashable, Codable {

/// Whether implementation supports dynamic registration.
public var dynamicRegistration: Bool?

/// Whether the clients supports related documents for document diagnostic pulls.
public var relatedDocumentSupport: Bool?
}

// MARK: Properties

public var synchronization: Synchronization? = nil
Expand Down Expand Up @@ -575,6 +585,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
public var semanticTokens: SemanticTokens? = nil

public var inlayHint: InlayHint? = nil

public var diagnostic: Diagnostic? = nil

public init(synchronization: Synchronization? = nil,
completion: Completion? = nil,
Expand All @@ -599,7 +611,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
foldingRange: FoldingRange? = nil,
callHierarchy: DynamicRegistrationCapability? = nil,
semanticTokens: SemanticTokens? = nil,
inlayHint: InlayHint? = nil) {
inlayHint: InlayHint? = nil,
diagnostic: Diagnostic? = nil) {
self.synchronization = synchronization
self.completion = completion
self.hover = hover
Expand All @@ -624,5 +637,6 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
self.callHierarchy = callHierarchy
self.semanticTokens = semanticTokens
self.inlayHint = inlayHint
self.diagnostic = diagnostic
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,30 @@ public struct InlayHintRegistrationOptions: RegistrationOptions, TextDocumentReg
}
}

/// Describe options to be used when registering for pull diagnostics. Since LSP 3.17.0
public struct DiagnosticRegistrationOptions: RegistrationOptions, TextDocumentRegistrationOptionsProtocol {
public var textDocumentRegistrationOptions: TextDocumentRegistrationOptions
public var diagnosticOptions: DiagnosticOptions

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

public func encodeIntoLSPAny(dict: inout [String: LSPAny]) {
textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict)

dict["interFileDependencies"] = .bool(diagnosticOptions.interFileDependencies)
dict["workspaceDiagnostics"] = .bool(diagnosticOptions.workspaceDiagnostics)
if let workDoneProgress = diagnosticOptions.workDoneProgress {
dict["workDoneProgress"] = .bool(workDoneProgress)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you also need to encode documentSelector (from TextDocumentRegistrationOptions in the LSP spec) and id (from StaticRegistrationOptions in the LSP spec) here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I couldn't get to this before the PR got merged. I will address it in my next PR when implementing the feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentSelector is already added to the dict by textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict) at least.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, and I apparently approved and merged the PR, forgotting that I still had a comment. If you could create a follow-up PR, that would be great.

}
}

/// 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 @@ -108,6 +108,9 @@ public struct ServerCapabilities: Codable, Hashable {
/// Whether the server supports the `textDocument/inlayHint` family of requests.
public var inlayHintProvider: ValueOrBool<InlayHintOptions>?

/// Whether the server supports the `textDocument/diagnostic` request.
public var diagnosticProvider: DiagnosticOptions?

/// Whether the server provides selection range support.
public var selectionRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>?

Expand Down Expand Up @@ -152,6 +155,7 @@ public struct ServerCapabilities: Codable, Hashable {
typeHierarchyProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
semanticTokensProvider: SemanticTokensOptions? = nil,
inlayHintProvider: ValueOrBool<InlayHintOptions>? = nil,
diagnosticProvider: DiagnosticOptions? = nil,
selectionRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
linkedEditingRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
monikerProvider: ValueOrBool<MonikerOptions>? = nil,
Expand Down Expand Up @@ -188,6 +192,7 @@ public struct ServerCapabilities: Codable, Hashable {
self.typeHierarchyProvider = typeHierarchyProvider
self.semanticTokensProvider = semanticTokensProvider
self.inlayHintProvider = inlayHintProvider
self.diagnosticProvider = diagnosticProvider
self.selectionRangeProvider = selectionRangeProvider
self.linkedEditingRangeProvider = linkedEditingRangeProvider
self.experimental = experimental
Expand Down
34 changes: 34 additions & 0 deletions Sources/SourceKitLSP/CapabilityRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public final class CapabilityRegistry {

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

/// Dynamically registered pull diagnostics options.
private var pullDiagnostics: [CapabilityRegistration: DiagnosticRegistrationOptions] = [:]

/// Dynamically registered file watchers.
private var didChangeWatchedFiles: DidChangeWatchedFilesRegistrationOptions?
Expand Down Expand Up @@ -60,6 +63,10 @@ public final class CapabilityRegistry {
clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true
}

public var clientHasDocumentDiagnosticsRegistration: Bool {
clientCapabilities.textDocument?.diagnostic?.dynamicRegistration == true
}

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

/// Dynamically register (pull model) diagnostic capabilities,
/// if the client supports it.
public func registerDiagnosticIfNeeded(
options: DiagnosticOptions,
for languages: [Language],
registerOnClient: ClientRegistrationHandler
) {
guard clientHasDocumentDiagnosticsRegistration else { return }
if let registration = registration(for: languages, in: pullDiagnostics) {
if options != registration.diagnosticOptions {
log("Unable to register new pull diagnostics options \(options) for " +
"\(languages) due to pre-existing options \(registration.diagnosticOptions)", level: .warning)
}
return
}
let registrationOptions = DiagnosticRegistrationOptions(
documentSelector: self.documentSelector(for: languages),
diagnosticOptions: options)
let registration = CapabilityRegistration(
method: DocumentDiagnosticsRequest.method,
registerOptions: self.encode(registrationOptions))

self.pullDiagnostics[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
4 changes: 4 additions & 0 deletions Sources/SourceKitLSP/Clang/ClangLanguageServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,10 @@ extension ClangLanguageServerShim {
forwardRequestToClangdOnQueue(req)
}

func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>) {
forwardRequestToClangdOnQueue(req)
}

func foldingRange(_ req: Request<FoldingRangeRequest>) {
queue.async {
if self.capabilities?.foldingRangeProvider?.isSupported == true {
Expand Down
15 changes: 15 additions & 0 deletions Sources/SourceKitLSP/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ public final class SourceKitServer: LanguageServer {
registerToolchainTextDocumentRequest(SourceKitServer.colorPresentation, [])
registerToolchainTextDocumentRequest(SourceKitServer.codeAction, nil)
registerToolchainTextDocumentRequest(SourceKitServer.inlayHint, [])
registerToolchainTextDocumentRequest(SourceKitServer.documentDiagnostic,
.full(.init(items: [])))
}

/// Register a `TextDocumentRequest` that requires a valid `Workspace`, `ToolchainLanguageServer`,
Expand Down Expand Up @@ -709,6 +711,11 @@ extension SourceKitServer {
self.dynamicallyRegisterCapability($0, registry)
}
}
if let diagnosticOptions = server.diagnosticProvider {
registry.registerDiagnosticIfNeeded(options: diagnosticOptions, for: languages) {
self.dynamicallyRegisterCapability($0, registry)
}
}
if let commandOptions = server.executeCommandProvider {
registry.registerExecuteCommandIfNeeded(commands: commandOptions.commands) {
self.dynamicallyRegisterCapability($0, registry)
Expand Down Expand Up @@ -1209,6 +1216,14 @@ extension SourceKitServer {
languageService.inlayHint(req)
}

func documentDiagnostic(
_ req: Request<DocumentDiagnosticsRequest>,
workspace: Workspace,
languageService: ToolchainLanguageServer
) {
languageService.documentDiagnostic(req)
}

/// Converts a location from the symbol index to an LSP location.
///
/// - Parameter location: The symbol index location
Expand Down
5 changes: 5 additions & 0 deletions Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,11 @@ extension SwiftLanguageServer {
}
}
}

public func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>) {
// TODO: Return provider object in initializeSync and implement pull-model document diagnostics here.
req.reply(.failure(.unknown("Pull-model diagnostics not implemented yet.")))
}

public func executeCommand(_ req: Request<ExecuteCommandRequest>) {
let params = req.params
Expand Down
1 change: 1 addition & 0 deletions Sources/SourceKitLSP/ToolchainLanguageServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public protocol ToolchainLanguageServer: AnyObject {
func colorPresentation(_ req: Request<ColorPresentationRequest>)
func codeAction(_ req: Request<CodeActionRequest>)
func inlayHint(_ req: Request<InlayHintRequest>)
func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>)

// MARK: - Other

Expand Down