Skip to content

Additions to the LSP module for workspace/didChangeWatchedFiles #376

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 5 commits into from
Mar 17, 2021
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
1 change: 1 addition & 0 deletions Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public let builtinNotifications: [NotificationType.Type] = [
CancelRequestNotification.self,
LogMessageNotification.self,
DidChangeConfigurationNotification.self,
DidChangeWatchedFilesNotification.self,
DidChangeWorkspaceFoldersNotification.self,
DidOpenTextDocumentNotification.self,
DidCloseTextDocumentNotification.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

/// Notification from the client when changes to watched files are detected.
///
/// - Parameter changes: The set of file changes.
public struct DidChangeWatchedFilesNotification: NotificationType {
public static let method: String = "workspace/didChangeWatchedFiles"

/// The file changes.
public var changes: [FileEvent]

public init(changes: [FileEvent]) {
self.changes = changes
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

import Foundation

/// Request sent from the server to the client to dynamically register for a new capability on the
/// client side.
///
/// Note that not all clients support dynamic registration and clients may provide dynamic
/// registration support for some capabilities but not others.
///
/// Servers must not register the same capability both statically through the initialization result
/// and dynamically. Servers that want to support both should check the client capabilities and only
/// register the capability statically if the client doesn't support dynamic registration for that
/// capability.
public struct RegisterCapabilityRequest: RequestType, Hashable {
public static let method: String = "client/registerCapability"
public typealias Response = VoidResponse

/// Capability registrations.
public var registrations: [Registration]

public init(registrations: [Registration]) {
self.registrations = registrations
}
}

/// General parameters to register a capability.
public struct Registration: Codable, Hashable {
/// The id used to register the capability which may be used to unregister support.
public var id: String

/// The method/capability to register for.
public var method: String

/// Options necessary for this registration.
public var registerOptions: LSPAny?

public init(id: String, method: String, registerOptions: LSPAny?) {
self.id = id
self.method = method
self.registerOptions = registerOptions
}

/// Create a new `Registration` with a randomly generated id. Save the generated
/// id if you wish to unregister the given registration.
public init(method: String, registerOptions: LSPAny?) {
self.id = UUID().uuidString
self.method = method
self.registerOptions = registerOptions
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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 sent from the server to the client to unregister a previously registered
/// capability.
public struct UnregisterCapabilityRequest: RequestType, Hashable {
public static let method: String = "client/unregisterCapability"
public typealias Response = VoidResponse

/// Capabilities to unregister.
public var unregistrations: [Unregistration]

public init(unregistrations: [Unregistration]) {
self.unregistrations = unregistrations
}
}

extension UnregisterCapabilityRequest: Codable {
private enum CodingKeys: String, CodingKey {
/// This should correctly be named `unregistrations`. However changing this
/// is a breaking change and needs to wait until the 4.x LSP spec update.
case unregistrations = "unregisterations"
}
}

/// General parameters to unregister a capability.
public struct Unregistration: Codable, Hashable {
/// The id used to unregister the capability, usually provided through the
/// register request.
public var id: String

/// The method/capability to unregister for.
public var method: String

public init(id: String, method: String) {
self.id = id
self.method = method
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ public struct WorkspaceClientCapabilities: Hashable, Codable {

public var didChangeConfiguration: DynamicRegistrationCapability? = nil

/// Whether the clients supports file watching - note that the protocol currently doesn't
/// support static registration for file changes.
public var didChangeWatchedFiles: DynamicRegistrationCapability? = nil

public var symbol: Symbol? = nil
Expand Down
39 changes: 39 additions & 0 deletions Sources/LanguageServerProtocol/SupportTypes/FileEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

/// An event describing a file change.
public struct FileEvent: Codable, Hashable {
public var uri: DocumentURI
public var type: FileChangeType

public init(uri: DocumentURI, type: FileChangeType) {
self.uri = uri
self.type = type
}
}
/// The type of file event.
///
/// In LSP, this is an integer, so we don't use a closed set.
public struct FileChangeType: RawRepresentable, Codable, Hashable {
public var rawValue: Int

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

/// The file was created.
public static let created: FileChangeType = FileChangeType(rawValue: 1)
/// The file was changed.
public static let changed: FileChangeType = FileChangeType(rawValue: 2)
/// The file was deleted.
public static let deleted: FileChangeType = FileChangeType(rawValue: 3)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//

/// Defines a watcher interested in specific file system change events.
public struct FileSystemWatcher: Codable, Hashable {
/// The glob pattern to watch.
public var globPattern: String

/// The kind of events of interest. If omitted it defaults to
/// WatchKind.create | WatchKind.change | WatchKind.delete.
public var kind: WatchKind?

public init(globPattern: String, kind: WatchKind? = nil) {
self.globPattern = globPattern
self.kind = kind
}
}

extension FileSystemWatcher: LSPAnyCodable {
public init?(fromLSPDictionary dictionary: [String : LSPAny]) {
guard let globPatternAny = dictionary[CodingKeys.globPattern.stringValue] else { return nil }
guard case .string(let globPattern) = globPatternAny else { return nil }
self.globPattern = globPattern

guard let kindValue = dictionary[CodingKeys.kind.stringValue] else {
self.kind = nil
return
}

switch kindValue {
case .null: self.kind = nil
case .int(let value): self.kind = WatchKind(rawValue: value)
default: return nil
}
}

public func encodeToLSPAny() -> LSPAny {
var encoded = [CodingKeys.globPattern.stringValue: LSPAny.string(globPattern)]
if let kind = kind {
encoded[CodingKeys.kind.stringValue] = LSPAny.int(kind.rawValue)
}
return .dictionary(encoded)
}
}

/// The type of file event a watcher is interested in.
///
/// In LSP, this is an integer, so we don't use a closed set.
public struct WatchKind: OptionSet, Codable, Hashable {
public var rawValue: Int

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

public static let create: WatchKind = WatchKind(rawValue: 1)
public static let change: WatchKind = WatchKind(rawValue: 2)
public static let delete: WatchKind = WatchKind(rawValue: 4)
}
49 changes: 49 additions & 0 deletions Sources/LanguageServerProtocol/SupportTypes/LSPAny.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,52 @@ public protocol LSPAnyCodable {
init?(fromLSPDictionary dictionary: [String: LSPAny])
func encodeToLSPAny() -> LSPAny
}

extension Optional: LSPAnyCodable where Wrapped: LSPAnyCodable {
public init?(fromLSPAny value: LSPAny) {
if case .null = value {
self = .none
return
}
guard case .dictionary(let dict) = value else {
return nil
}
guard let wrapped = Wrapped.init(fromLSPDictionary: dict) else {
return nil
}
self = .some(wrapped)
}

public init?(fromLSPDictionary dictionary: [String : LSPAny]) {
return nil
}

public func encodeToLSPAny() -> LSPAny {
guard let wrapped = self else { return .null }
return wrapped.encodeToLSPAny()
Copy link
Contributor

@benlangmuir benlangmuir Mar 16, 2021

Choose a reason for hiding this comment

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

Do we actually use the null encoding anywhere, or is this just for completeness? If possible I strongly prefer skipping nil values instead of encoding them as null since that seems to play nicer with other tools and saves space.

Edit: to be clear, I'm fine with keeping this conformance of Optional: LSPAnyCodable, particularly so that we can decode it correctly, I'm just wondering if it's used concretely in our code anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Swapped over the place that used it

}
}

extension Array: LSPAnyCodable where Element: LSPAnyCodable {
public init?(fromLSPArray array: LSPAny) {
guard case .array(let array) = array else {
return nil
}
var result = [Element]()
for case .dictionary(let editDict) in array {
guard let element = Element.init(fromLSPDictionary: editDict) else {
return nil
}
result.append(element)
}
self = result
}

public init?(fromLSPDictionary dictionary: [String : LSPAny]) {
return nil
}

public func encodeToLSPAny() -> LSPAny {
return .array(map { $0.encodeToLSPAny() })
}
}
Loading