├── .gitignore ├── .travis.yml ├── .vscode ├── cSpell.json ├── settings.json └── tasks.json ├── Dockerfile ├── License ├── Package.swift ├── ReadMe.md ├── Scripts ├── InstallSwift.sh └── TestLinux.sh ├── Sources ├── JsonRpcProtocol │ ├── Decoders.swift │ ├── Encoders.swift │ ├── JsonRpcProtocol.swift │ └── Serialization.swift └── LanguageServerProtocol │ ├── Cancellation.swift │ ├── Capabilities.swift │ ├── ClientRegistration.swift │ ├── Command.swift │ ├── Completion.swift │ ├── Diagnostics.swift │ ├── ErrorCodes.swift │ ├── Formatting.swift │ ├── Initialization.swift │ ├── InputOutput │ └── StandardInputOutputBuffer.swift │ ├── LanguageServerProtocol.swift │ ├── MessageLogging.swift │ ├── SwiftFixes.swift │ ├── TextDocument.swift │ └── Workspace.swift └── Tests ├── JsonRpcProtocolTests └── EncodableTests.swift ├── LanguageServerProtocolTests └── MessageBufferTests.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | Packages/ 38 | .build/ 39 | Package.pins 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | .DS_Store 68 | *.xcodeproj 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: linux 4 | dist: trusty 5 | sudo: required 6 | language: cpp 7 | - os: osx 8 | osx_image: xcode8.3 9 | language: objective-c 10 | sudo: required 11 | 12 | script: 13 | - swift build 14 | - swift test 15 | 16 | before_install: 17 | - chmod ugo+x ./Scripts/InstallSwift.sh 18 | - . ./Scripts/InstallSwift.sh 19 | 20 | notifications: 21 | email: 22 | on_success: never 23 | on_failure: change -------------------------------------------------------------------------------- /.vscode/cSpell.json: -------------------------------------------------------------------------------- 1 | // cSpell Settings 2 | { 3 | // Version of the setting file. Always 0.1 4 | "version": "0.1", 5 | // language - current active spelling language 6 | "language": "en", 7 | // words - list of words to be always considered correct 8 | "words": [ 9 | "langsrv", 10 | "jsonprc", 11 | "langsrvlib", 12 | "swiftlangsrv", 13 | "errno", 14 | "FILENO", 15 | "inout", 16 | "standardinputbuffer", 17 | "params", 18 | "stringify", 19 | "unregister", 20 | "Encodable", 21 | "Decodable", 22 | "Glibc", 23 | "languageserverprotocol", 24 | "Unregistration", 25 | "deregister", 26 | "unregisterations", 27 | "jsonrpcprotocol", 28 | "encodables" 29 | ], 30 | // flagWords - list of words to be always considered incorrect 31 | // This is useful for offensive words and common spelling errors. 32 | // For example "hte" should be "the" 33 | "flagWords": [ 34 | "hte" 35 | ] 36 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | ".build": true, 9 | "Package.pins": true 10 | }, 11 | "search.exclude": { 12 | "**/node_modules": true, 13 | "**/bower_components": true, 14 | ".build": true, 15 | "Package.pins": true 16 | }, 17 | "editor.quickSuggestions": false, 18 | "cSpell.enabled": true 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "0.1.0", 5 | "command": "swift", 6 | "isShellCommand": true, 7 | "args": ["build"], 8 | "showOutput": "always" 9 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM swift:3.1 2 | 3 | WORKDIR /package 4 | 5 | COPY . ./ 6 | 7 | RUN swift package fetch 8 | RUN swift package clean 9 | CMD swift test --parallel -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Kiad Studios, LLC. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:3.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "LanguageServerProtocol", 7 | targets: [ 8 | Target(name: "LanguageServerProtocol"), 9 | Target(name: "JsonRpcProtocol", dependencies: ["LanguageServerProtocol"]) 10 | ], 11 | dependencies: [ 12 | .Package(url: "https://github.com/owensd/json-swift.git", majorVersion: 2, minor: 0) 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # Language Server Protocol 2 | 3 | ![travis-ci badge status](https://travis-ci.org/owensd/swift-lsp.svg?branch=master) 4 | 5 | Welcome to the Swift implementation of the [Language Server Protocol][1]. This provides a complete 6 | implementation of v3.0 of the spec. The intention is to allow for the building of language servers 7 | with the Swift language. 8 | 9 | There are two other projects that serve as an illustration on how to integrate with 10 | [Visual Studio Code][2]: 11 | - [vscode-swift][3] - The VS Code hosting integration. This packages [swift-langsrv] and includes 12 | other Swift language enhancements, such as syntax highlighting and snippets. 13 | - [swift-langsrv][4] - The Swift Language Server based on this library. 14 | 15 | ## Design Breakdown 16 | 17 | The system is broken up into parts that are designed to be interchangeable. Each layer of the 18 | system is agnostic of the other layers, except for the data contracts that between each layer. 19 | The entirety of the contract is defined in `LanguageServerProtocol.swift`. 20 | 21 | The basic flow through the system looks like this: 22 | 23 | An `InputBuffer` receives data from a some source, say `stdin`. It takes that data and converts 24 | it into a `Message` based on the spec. That message is then translated by an implementation of 25 | a `MessageProtocol` type, say `JsonRpcProtocol`. At this point, the raw message has been 26 | converted into a transport-agnostic `LanguageServerCommand` that can be used by any implementation 27 | of your own language server type. 28 | 29 | The design of this system to both conform to the [Language Server Protocol][1] spec while also 30 | providing some leeway on how each of the layers interact. For instance, with this design, it is 31 | completely possible to change the input source to be IPC and the message format to be a binary 32 | representation. 33 | 34 | > Copyright (c) Kiad Studios, LLC. All rights reserved. 35 | > Licensed under the MIT License. See License in the project root for license information. 36 | 37 | [1]: https://github.com/Microsoft/language-server-protocol 38 | [2]: https://code.visualstudio.com 39 | [3]: https://github.com/owensd/vscode-swift 40 | [4]: https://github.com/owensd/swift-langsrv 41 | -------------------------------------------------------------------------------- /Scripts/InstallSwift.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 4 | DIR="$(pwd)" 5 | cd .. 6 | export SWIFT_VERSION=swift-3.1.1-RELEASE 7 | wget https://swift.org/builds/swift-3.1.1-release/ubuntu1404/${SWIFT_VERSION}/${SWIFT_VERSION}-ubuntu14.04.tar.gz 8 | tar xzf $SWIFT_VERSION-ubuntu14.04.tar.gz 9 | export PATH="${PWD}/${SWIFT_VERSION}-ubuntu14.04/usr/bin:${PATH}" 10 | cd "$DIR" 11 | else 12 | export SWIFT_VERSION=swift-3.1.1-RELEASE 13 | curl -O https://swift.org/builds/swift-3.1.1-release/xcode/${SWIFT_VERSION}/${SWIFT_VERSION}-osx.pkg 14 | sudo installer -pkg ${SWIFT_VERSION}-osx.pkg -target / 15 | export TOOLCHAINS=swift 16 | fi 17 | -------------------------------------------------------------------------------- /Scripts/TestLinux.sh: -------------------------------------------------------------------------------- 1 | docker run --rm \ 2 | --volume "$(pwd):/package" \ 3 | --workdir "/package" \ 4 | swift:3.1 \ 5 | /bin/bash -c \ 6 | "swift package update && swift test --build-path ./.build/linux" -------------------------------------------------------------------------------- /Sources/JsonRpcProtocol/Decoders.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import LanguageServerProtocol 7 | 8 | #if os(macOS) 9 | import os.log 10 | #endif 11 | 12 | import JSONLib 13 | 14 | @available(macOS 10.12, *) 15 | fileprivate let log = OSLog(subsystem: "com.kiadstudios.languageserverprotocol", category: "Serialization") 16 | 17 | extension Bool: Decodable { 18 | public typealias EncodableType = JSValue 19 | 20 | public static func decode(_ data: JSValue?) throws -> Bool { 21 | guard let value = data.bool else { 22 | throw "Value is not of type `Bool`." 23 | } 24 | return value 25 | } 26 | } 27 | 28 | extension String: Decodable { 29 | public typealias EncodableType = JSValue 30 | 31 | public static func decode(_ data: JSValue?) throws -> String { 32 | guard let value = data.string else { 33 | throw "Value is not of type `String`." 34 | } 35 | return value 36 | } 37 | } 38 | 39 | extension InitializeParams { 40 | public static func decode(_ data: JSValue?) throws -> InitializeParams { 41 | guard let _ = data.object else { throw "The `params` value must be a dictionary." } 42 | let processId = data["processId"].integer ?? nil 43 | let rootPath = data["rootPath"].string ?? nil 44 | let rootUri = data["rootUri"].string ?? nil 45 | // TODO(owensd): Support user options... 46 | //let initializationOptions = try type(of: initializationOptions).decode(json["initializationOptions"]) 47 | let initializationOptions: Any? = nil 48 | let capabilities = try ClientCapabilities.decode(data["capabilities"]) 49 | let trace = try TraceSetting.decode(data["trace"]) 50 | 51 | return InitializeParams( 52 | processId: processId, 53 | rootPath: rootPath, 54 | rootUri: rootUri, 55 | initializationOptions: initializationOptions, 56 | capabilities: capabilities, 57 | trace: trace 58 | ) 59 | } 60 | } 61 | 62 | extension TraceSetting: Decodable { 63 | public typealias EncodableType = JSValue 64 | 65 | public static func decode(_ data: JSValue?) throws -> TraceSetting { 66 | guard let value = data.string else { 67 | throw "The trace setting must be a string or not present." 68 | } 69 | switch value { 70 | case "off": return .off 71 | case "messages": return .messages 72 | case "verbose": return .verbose 73 | default: throw "'\(value)' is an unsupported value" 74 | } 75 | } 76 | } 77 | 78 | extension RequestId: Decodable { 79 | public typealias EncodableType = JSValue 80 | 81 | public static func decode(_ data: JSValue?) throws -> RequestId { 82 | if let value = data.string { 83 | return .string(value) 84 | } 85 | if let value = data.number { 86 | return .number(Int(value)) 87 | } 88 | 89 | throw "A request ID must be a string or a number." 90 | } 91 | } 92 | 93 | extension CancelParams: Decodable { 94 | public typealias EncodableType = JSValue 95 | 96 | public static func decode(_ data: JSValue?) throws -> CancelParams { 97 | return CancelParams(id: try RequestId.decode(data["id"])) 98 | } 99 | } 100 | 101 | extension ClientCapabilities: Decodable { 102 | public typealias EncodableType = JSValue 103 | 104 | public static func decode(_ data: JSValue?) throws -> ClientCapabilities { 105 | let workspace = try? WorkspaceClientCapabilities.decode(data["workspace"]) 106 | let textDocument = try? TextDocumentClientCapabilities.decode(data["workspace"]) 107 | let experimental = data["experimental"] ?? .null 108 | 109 | return ClientCapabilities( 110 | workspace: workspace, 111 | textDocument: textDocument, 112 | experimental: experimental) 113 | } 114 | } 115 | 116 | extension TextDocumentClientCapabilities: Decodable { 117 | public typealias EncodableType = JSValue 118 | 119 | public static func decode(_ data: JSValue?) throws -> TextDocumentClientCapabilities { 120 | if let _ = data.object { 121 | return TextDocumentClientCapabilities() 122 | } 123 | 124 | throw "The `textDocument` key is not a valid object." 125 | } 126 | } 127 | 128 | extension ShowMessageParams: Decodable { 129 | public typealias EncodableType = JSValue 130 | 131 | public static func decode(_ data: JSValue?) throws -> ShowMessageParams { 132 | guard let type = try? MessageType.decode(data["type"]) else { 133 | throw "The `type` parameter is required." 134 | } 135 | guard let message = data["message"].string else { 136 | throw "The `message` parameter is required." 137 | } 138 | 139 | return ShowMessageParams(type: type, message: message) 140 | } 141 | } 142 | 143 | extension ShowMessageRequestParams: Decodable { 144 | public typealias EncodableType = JSValue 145 | 146 | public static func decode(_ data: JSValue?) throws -> ShowMessageRequestParams { 147 | guard let type = try? MessageType.decode(data["type"]) else { 148 | throw "The `type` parameter is required." 149 | } 150 | guard let message = data["message"].string else { 151 | throw "The `message` parameter is required." 152 | } 153 | 154 | var actions: [MessageActionItem]? = nil 155 | if let values = data["actions"].array { 156 | actions = try values.map(MessageActionItem.decode) 157 | } 158 | 159 | return ShowMessageRequestParams(type: type, message: message, actions: actions) 160 | } 161 | } 162 | 163 | extension LogMessageParams: Decodable { 164 | public typealias EncodableType = JSValue 165 | 166 | public static func decode(_ data: JSValue?) throws -> LogMessageParams { 167 | guard let type = try? MessageType.decode(data["type"]) else { 168 | throw "The `type` parameter is required." 169 | } 170 | guard let message = data["message"].string else { 171 | throw "The `message` parameter is required." 172 | } 173 | 174 | return LogMessageParams(type: type, message: message) 175 | } 176 | } 177 | 178 | extension RegistrationParams: Decodable { 179 | public typealias EncodableType = JSValue 180 | 181 | public static func decode(_ data: JSValue?) throws -> RegistrationParams { 182 | guard let values = data["registrations"].array else { 183 | throw "The `registrations` parameter is required." 184 | } 185 | 186 | let registrations = try values.map(Registration.decode) 187 | return RegistrationParams(registrations: registrations) 188 | } 189 | } 190 | 191 | extension Registration: Decodable { 192 | public typealias EncodableType = JSValue 193 | 194 | public static func decode(_ data: JSValue?) throws -> Registration { 195 | guard let id = data["id"].string else { 196 | throw "The `id` parameter is required." 197 | } 198 | guard let method = data["method"].string else { 199 | throw "The `method` parameter is required." 200 | } 201 | 202 | return Registration(id: id, method: method, registerOptions: data["registerOptions"]) 203 | } 204 | } 205 | 206 | extension UnregistrationParams: Decodable { 207 | public typealias EncodableType = JSValue 208 | 209 | public static func decode(_ data: JSValue?) throws -> UnregistrationParams { 210 | guard let values = data["unregisterations"].array else { 211 | throw "The `unregisterations` parameter is required." 212 | } 213 | 214 | let unregisterations = try values.map(Unregistration.decode) 215 | return UnregistrationParams(unregisterations: unregisterations) 216 | } 217 | } 218 | 219 | extension Unregistration: Decodable { 220 | public typealias EncodableType = JSValue 221 | 222 | public static func decode(_ data: JSValue?) throws -> Unregistration { 223 | guard let id = data["id"].string else { 224 | throw "The `id` parameter is required." 225 | } 226 | guard let method = data["method"].string else { 227 | throw "The `method` parameter is required." 228 | } 229 | 230 | return Unregistration(id: id, method: method) 231 | } 232 | } 233 | 234 | extension DidChangeConfigurationParams: Decodable { 235 | public typealias EncodableType = JSValue 236 | 237 | public static func decode(_ data: JSValue?) throws -> DidChangeConfigurationParams { 238 | let encodable = (data?["settings"] ?? .null) 239 | return DidChangeConfigurationParams(settings: encodable) 240 | } 241 | } 242 | 243 | extension DidChangeWatchedFilesParams: Decodable { 244 | public typealias EncodableType = JSValue 245 | 246 | public static func decode(_ data: JSValue?) throws -> DidChangeWatchedFilesParams { 247 | guard let changes = data["changes"].array else { 248 | throw "The `changes` property is required." 249 | } 250 | 251 | let events = try changes.map(FileEvent.decode) 252 | return DidChangeWatchedFilesParams(changes: events) 253 | } 254 | } 255 | 256 | extension FileEvent: Decodable { 257 | public typealias EncodableType = JSValue 258 | 259 | public static func decode(_ data: JSValue?) throws -> FileEvent { 260 | guard let uri = data["uri"].string else { 261 | throw "The `uri` parameter is required." 262 | } 263 | 264 | let type = try FileChangeType.decode(data["type"]) 265 | return FileEvent(uri: uri, type: type) 266 | } 267 | } 268 | 269 | extension FileChangeType: Decodable { 270 | public typealias EncodableType = JSValue 271 | 272 | public static func decode(_ data: JSValue?) throws -> FileChangeType { 273 | guard let number = data.integer else { 274 | throw "The parameter must be a number." 275 | } 276 | 277 | guard let type = FileChangeType(rawValue: number) else { 278 | throw "The value `\(number)` is not a supported change type." 279 | } 280 | 281 | return type 282 | } 283 | } 284 | 285 | extension WorkspaceSymbolParams: Decodable { 286 | public typealias EncodableType = JSValue 287 | 288 | public static func decode(_ data: JSValue?) throws -> WorkspaceSymbolParams { 289 | guard let query = data["query"].string else { 290 | throw "The `query` parameter is required." 291 | } 292 | 293 | return WorkspaceSymbolParams(query: query) 294 | } 295 | } 296 | 297 | extension ExecuteCommandParams: Decodable { 298 | public typealias EncodableType = JSValue 299 | 300 | public static func decode(_ data: JSValue?) throws -> ExecuteCommandParams { 301 | guard let command = data["command"].string else { 302 | throw "The `command` parameter is required." 303 | } 304 | 305 | var arguments: [String]? = nil 306 | if let args = data["arguments"].array { 307 | arguments = args.map { $0.string! } 308 | } 309 | 310 | return ExecuteCommandParams(command: command, arguments: arguments) 311 | } 312 | } 313 | 314 | extension ApplyWorkspaceEditParams: Decodable { 315 | public typealias EncodableType = JSValue 316 | 317 | public static func decode(_ data: JSValue?) throws -> ApplyWorkspaceEditParams { 318 | let edit = try WorkspaceEdit.decode(data["edit"]) 319 | return ApplyWorkspaceEditParams(edit: edit) 320 | } 321 | } 322 | 323 | extension WorkspaceEdit: Decodable { 324 | public typealias EncodableType = JSValue 325 | 326 | public static func decode(_ data: JSValue?) throws -> WorkspaceEdit { 327 | if let _ = data["changes"].object { 328 | throw "The `changes` parameter is not currently implemented." 329 | } 330 | else if let documentChanges = data["documentChanges"].array { 331 | let changes = try documentChanges.map(TextDocumentEdit.decode) 332 | return .documentChanges(changes) 333 | } 334 | 335 | throw "A `changes` or `documentChanges` parameter is required." 336 | } 337 | } 338 | 339 | extension TextDocumentEdit: Decodable { 340 | public typealias EncodableType = JSValue 341 | 342 | public static func decode(_ data: JSValue?) throws -> TextDocumentEdit { 343 | let textDocument = try VersionedTextDocumentIdentifier.decode(data["textDocument"]) 344 | guard let values = data["edits"].array else { 345 | throw "The `edits` parameter is required." 346 | } 347 | let edits = try values.map(TextEdit.decode) 348 | return TextDocumentEdit(textDocument: textDocument, edits: edits) 349 | } 350 | } 351 | 352 | extension TextDocumentIdentifier: Decodable { 353 | public typealias EncodableType = JSValue 354 | 355 | public static func decode(_ data: JSValue?) throws -> TextDocumentIdentifier { 356 | guard let uri = data["uri"].string else { 357 | throw "The `uri` parameter is required." 358 | } 359 | return TextDocumentIdentifier(uri: uri) 360 | } 361 | } 362 | 363 | extension VersionedTextDocumentIdentifier: Decodable { 364 | public typealias EncodableType = JSValue 365 | 366 | public static func decode(_ data: JSValue?) throws -> VersionedTextDocumentIdentifier { 367 | let uri = try TextDocumentIdentifier.decode(data).uri 368 | guard let version = data["version"].integer else { 369 | throw "The `version` parameter is required." 370 | } 371 | 372 | return VersionedTextDocumentIdentifier(uri: uri, version: version) 373 | } 374 | } 375 | 376 | extension TextEdit: Decodable { 377 | public typealias EncodableType = JSValue 378 | 379 | public static func decode(_ data: JSValue?) throws -> TextEdit { 380 | let range = try Range.decode(data["range"]) 381 | guard let newText = data["newText"].string else { 382 | throw "The `newText` parameter is required." 383 | } 384 | 385 | return TextEdit(range: range, newText: newText) 386 | } 387 | } 388 | 389 | extension LanguageServerProtocol.Range: Decodable { 390 | public typealias EncodableType = JSValue 391 | 392 | public static func decode(_ data: JSValue?) throws -> LanguageServerProtocol.Range { 393 | let start = try Position.decode(data["start"]) 394 | let end = try Position.decode(data["end"]) 395 | return Range(start: start, end: end) 396 | } 397 | } 398 | 399 | extension Position: Decodable { 400 | public typealias EncodableType = JSValue 401 | 402 | public static func decode(_ data: JSValue?) throws -> Position { 403 | guard let line = data["line"].integer else { 404 | throw "The `line` parameter is required." 405 | } 406 | guard let character = data["character"].integer else { 407 | throw "The `character` parameter is required." 408 | } 409 | 410 | return Position(line: line, character: character) 411 | } 412 | } 413 | 414 | extension PublishDiagnosticsParams: Decodable { 415 | public typealias EncodableType = JSValue 416 | 417 | public static func decode(_ data: JSValue?) throws -> PublishDiagnosticsParams { 418 | guard let uri = data["uri"].string else { 419 | throw "The `uri` parameter is required." 420 | } 421 | guard let values = data["diagnostics"].array else { 422 | throw "The `diagnostics` parameter is required." 423 | } 424 | let diagnostics = try values.map(Diagnostic.decode) 425 | return PublishDiagnosticsParams(uri: uri, diagnostics: diagnostics) 426 | } 427 | } 428 | 429 | extension Diagnostic: Decodable { 430 | public typealias EncodableType = JSValue 431 | 432 | public static func decode(_ data: JSValue?) throws -> Diagnostic { 433 | let range = try Range.decode(data["range"]) 434 | guard let message = data["message"].string else { 435 | throw "The `message` parameter is required." 436 | } 437 | 438 | let severity = try? DiagnosticSeverity.decode(data["severity"]) 439 | let code = try? DiagnosticCode.decode(data["code"]) 440 | let source = data["source"].string 441 | 442 | return Diagnostic( 443 | range: range, 444 | message: message, 445 | severity: severity, 446 | code: code, 447 | source: source 448 | ) 449 | } 450 | } 451 | 452 | extension DiagnosticSeverity: Decodable { 453 | public typealias EncodableType = JSValue 454 | 455 | public static func decode(_ data: JSValue?) throws -> DiagnosticSeverity { 456 | guard let number = data.integer else { 457 | throw "The parameter must be an integer." 458 | } 459 | 460 | guard let severity = DiagnosticSeverity(rawValue: number) else { 461 | throw "The severity is value is not supported." 462 | } 463 | return severity 464 | } 465 | } 466 | 467 | extension DiagnosticCode: Decodable { 468 | public typealias EncodableType = JSValue 469 | 470 | public static func decode(_ data: JSValue?) throws -> DiagnosticCode { 471 | if let number = data.integer { 472 | return .number(number) 473 | } 474 | if let string = data.string { 475 | return .string(string) 476 | } 477 | 478 | throw "Parameter is not of the expected type." 479 | } 480 | } 481 | 482 | extension DidOpenTextDocumentParams: Decodable { 483 | public typealias EncodableType = JSValue 484 | 485 | public static func decode(_ data: JSValue?) throws -> DidOpenTextDocumentParams { 486 | let textDocument = try TextDocumentItem.decode(data["textDocument"]) 487 | return DidOpenTextDocumentParams(textDocument: textDocument) 488 | } 489 | } 490 | 491 | extension TextDocumentItem: Decodable { 492 | public typealias EncodableType = JSValue 493 | 494 | public static func decode(_ data: JSValue?) throws -> TextDocumentItem { 495 | guard let uri = data["uri"].string else { 496 | throw "The `uri` parameter is required." 497 | } 498 | 499 | guard let languageId = data["languageId"].string else { 500 | throw "The `languageId` parameter is required." 501 | } 502 | 503 | guard let version = data["version"].integer else { 504 | throw "The `version` parameter is required." 505 | } 506 | 507 | guard let text = data["text"].string else { 508 | throw "The `text` parameter is required." 509 | } 510 | 511 | return TextDocumentItem( 512 | uri: uri, 513 | languageId: languageId, 514 | version: version, 515 | text: text 516 | ) 517 | } 518 | } 519 | 520 | extension DidChangeTextDocumentParams: Decodable { 521 | public typealias EncodableType = JSValue 522 | 523 | public static func decode(_ data: JSValue?) throws -> DidChangeTextDocumentParams { 524 | let textDocument = try VersionedTextDocumentIdentifier.decode(data["textDocument"]) 525 | guard let changes = data["contentChanges"].array else { 526 | throw "The `contentChanges` parameter is required." 527 | } 528 | let events = try changes.map(TextDocumentContentChangeEvent.decode) 529 | return DidChangeTextDocumentParams( 530 | textDocument: textDocument, 531 | contentChanges: events 532 | ) 533 | } 534 | } 535 | 536 | extension TextDocumentContentChangeEvent: Decodable { 537 | public typealias EncodableType = JSValue 538 | 539 | public static func decode(_ data: JSValue?) throws -> TextDocumentContentChangeEvent { 540 | guard let text = data["text"].string else { 541 | throw "The `text` parameter is required." 542 | } 543 | 544 | let range = try? LanguageServerProtocol.Range.decode(data["range"]) 545 | let rangeLength = data["rangeLength"].integer 546 | 547 | return TextDocumentContentChangeEvent( 548 | text: text, 549 | range: range, 550 | rangeLength: rangeLength 551 | ) 552 | } 553 | } 554 | 555 | extension WillSaveTextDocumentParams: Decodable { 556 | public typealias EncodableType = JSValue 557 | 558 | public static func decode(_ data: JSValue?) throws -> WillSaveTextDocumentParams { 559 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 560 | let reason = try TextDocumentSaveReason.decode(data["reason"]) 561 | 562 | return WillSaveTextDocumentParams( 563 | textDocument: textDocument, 564 | reason: reason 565 | ) 566 | } 567 | } 568 | 569 | extension TextDocumentSaveReason: Decodable { 570 | public typealias EncodableType = JSValue 571 | 572 | public static func decode(_ data: JSValue?) throws -> TextDocumentSaveReason { 573 | guard let value = data.integer else { 574 | throw "The parameter must be an integer." 575 | } 576 | 577 | guard let reason = TextDocumentSaveReason(rawValue: value) else { 578 | throw "The value `\(value)` is not a valid save reason." 579 | } 580 | return reason 581 | } 582 | } 583 | 584 | extension DidSaveTextDocumentParams: Decodable { 585 | public typealias EncodableType = JSValue 586 | 587 | public static func decode(_ data: JSValue?) throws -> DidSaveTextDocumentParams { 588 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 589 | let text = data["text"].string 590 | 591 | return DidSaveTextDocumentParams( 592 | textDocument: textDocument, 593 | text: text 594 | ) 595 | } 596 | } 597 | 598 | extension DidCloseTextDocumentParams: Decodable { 599 | public typealias EncodableType = JSValue 600 | 601 | public static func decode(_ data: JSValue?) throws -> DidCloseTextDocumentParams { 602 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 603 | return DidCloseTextDocumentParams(textDocument: textDocument) 604 | } 605 | } 606 | 607 | extension CompletionItem: Decodable { 608 | public typealias EncodableType = JSValue 609 | 610 | public static func decode(_ data: JSValue?) throws -> CompletionItem { 611 | guard let label = data["label"].string else { 612 | throw "The `label` parameter is required." 613 | } 614 | 615 | let kind = try? CompletionItemKind.decode(data["kind"]) 616 | let detail = data["detail"].string 617 | let documentation = data["documentation"].string 618 | let sortText = data["sortText"].string 619 | let filterText = data["filterText"].string 620 | let insertText = data["insertText"].string 621 | let insertTextFormat: InsertTextFormat? = nil 622 | let textEdit = try? TextEdit.decode(data["textEdit"]) 623 | 624 | var additionalEdits: [TextEdit]? = nil 625 | if let values = data["additionalTextEdits"].array { 626 | additionalEdits = try values.map(TextEdit.decode) 627 | } 628 | 629 | let command = try? Command.decode(data["command"]) 630 | let data = data["data"] 631 | 632 | return CompletionItem( 633 | label: label, 634 | kind: kind, 635 | detail: detail, 636 | documentation: documentation, 637 | sortText: sortText, 638 | filterText: filterText, 639 | insertText: insertText, 640 | insertTextFormat: insertTextFormat, 641 | textEdit: textEdit, 642 | additionalTextEdits: additionalEdits, 643 | command: command, 644 | data: data 645 | ) 646 | } 647 | } 648 | 649 | extension CompletionItemKind: Decodable { 650 | public typealias EncodableType = JSValue 651 | 652 | public static func decode(_ data: JSValue?) throws -> CompletionItemKind { 653 | guard let number = data.integer else { 654 | throw "The parameter must be an integer." 655 | } 656 | 657 | guard let kind = CompletionItemKind(rawValue: number) else { 658 | throw "The value `\(number)` is not a valid completion kind." 659 | } 660 | return kind 661 | } 662 | } 663 | 664 | extension Command: Decodable { 665 | public typealias EncodableType = JSValue 666 | 667 | public static func decode(_ data: JSValue?) throws -> Command { 668 | guard let title = data["title"].string else { 669 | throw "The `title` parameter is required." 670 | } 671 | 672 | guard let command = data["command"].string else { 673 | throw "The `command` parameter is required." 674 | } 675 | 676 | return Command( 677 | title: title, 678 | command: command, 679 | arguments: (data["arguments"] ?? .null) 680 | ) 681 | } 682 | } 683 | 684 | extension TextDocumentPositionParams: Decodable { 685 | public typealias EncodableType = JSValue 686 | 687 | public static func decode(_ data: JSValue?) throws -> TextDocumentPositionParams { 688 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 689 | let position = try Position.decode(data["position"]) 690 | return TextDocumentPositionParams( 691 | textDocument: textDocument, 692 | position: position 693 | ) 694 | } 695 | } 696 | 697 | extension ReferenceParams: Decodable { 698 | public typealias EncodableType = JSValue 699 | 700 | public static func decode(_ data: JSValue?) throws -> ReferenceParams { 701 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 702 | let position = try Position.decode(data["position"]) 703 | let context = try ReferenceContext.decode(data["context"]) 704 | return ReferenceParams( 705 | textDocument: textDocument, 706 | position: position, 707 | context: context 708 | ) 709 | } 710 | } 711 | 712 | extension ReferenceContext: Decodable { 713 | public typealias EncodableType = JSValue 714 | 715 | public static func decode(_ data: JSValue?) throws -> ReferenceContext { 716 | guard let include = data["includeDeclaration"].bool else { 717 | throw "The `include` parameter is required." 718 | } 719 | 720 | return ReferenceContext(includeDeclaration: include) 721 | } 722 | } 723 | 724 | extension DocumentSymbolParams: Decodable { 725 | public typealias EncodableType = JSValue 726 | 727 | public static func decode(_ data: JSValue?) throws -> DocumentSymbolParams { 728 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 729 | return DocumentSymbolParams(textDocument: textDocument) 730 | } 731 | } 732 | 733 | extension DocumentFormattingParams: Decodable { 734 | public typealias EncodableType = JSValue 735 | 736 | public static func decode(_ data: JSValue?) throws -> DocumentFormattingParams { 737 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 738 | let options = try FormattingOptions.decode(data["options"]) 739 | return DocumentFormattingParams( 740 | textDocument: textDocument, 741 | options: options 742 | ) 743 | } 744 | } 745 | 746 | extension FormattingOptions: Decodable { 747 | public typealias EncodableType = JSValue 748 | 749 | public static func decode(_ data: JSValue?) throws -> FormattingOptions { 750 | guard let tabSize = data["tabSize"].integer else { 751 | throw "The `tabSize` parameter is required." 752 | } 753 | guard let insertSpaces = data["insertSpaces"].bool else { 754 | throw "The `insertSpaces` parameter is required." 755 | } 756 | 757 | return FormattingOptions( 758 | tabSize: tabSize, 759 | insertSpaces: insertSpaces 760 | ) 761 | } 762 | } 763 | 764 | extension DocumentRangeFormattingParams: Decodable { 765 | public typealias EncodableType = JSValue 766 | 767 | public static func decode(_ data: JSValue?) throws -> DocumentRangeFormattingParams { 768 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 769 | let range = try LanguageServerProtocol.Range.decode(data["range"]) 770 | let options = try FormattingOptions.decode(data["options"]) 771 | return DocumentRangeFormattingParams( 772 | textDocument: textDocument, 773 | range: range, 774 | options: options 775 | ) 776 | } 777 | } 778 | 779 | extension DocumentOnTypeFormattingParams: Decodable { 780 | public typealias EncodableType = JSValue 781 | 782 | public static func decode(_ data: JSValue?) throws -> DocumentOnTypeFormattingParams { 783 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 784 | let position = try Position.decode(data["position"]) 785 | guard let ch = data["ch"].string else { 786 | throw "The `ch` parameter is required." 787 | } 788 | let options = try FormattingOptions.decode(data["options"]) 789 | return DocumentOnTypeFormattingParams( 790 | textDocument: textDocument, 791 | position: position, 792 | ch: ch, 793 | options: options 794 | ) 795 | } 796 | } 797 | 798 | extension CodeActionParams: Decodable { 799 | public typealias EncodableType = JSValue 800 | 801 | public static func decode(_ data: JSValue?) throws -> CodeActionParams { 802 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 803 | let range = try LanguageServerProtocol.Range.decode(data["range"]) 804 | let context = try CodeActionContext.decode(data["context"]) 805 | return CodeActionParams( 806 | textDocument: textDocument, 807 | range: range, 808 | context: context 809 | ) 810 | } 811 | } 812 | 813 | extension CodeActionContext: Decodable { 814 | public typealias EncodableType = JSValue 815 | 816 | public static func decode(_ data: JSValue?) throws -> CodeActionContext { 817 | guard let values = data["diagnostics"].array else { 818 | throw "The `diagnostics` parameter is required." 819 | } 820 | return CodeActionContext( 821 | diagnostics: try values.map(Diagnostic.decode) 822 | ) 823 | } 824 | } 825 | 826 | extension CodeLensParams: Decodable { 827 | public typealias EncodableType = JSValue 828 | 829 | public static func decode(_ data: JSValue?) throws -> CodeLensParams { 830 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 831 | return CodeLensParams(textDocument: textDocument) 832 | } 833 | } 834 | 835 | extension CodeLens: Decodable { 836 | public typealias EncodableType = JSValue 837 | 838 | public static func decode(_ data: JSValue?) throws -> CodeLens { 839 | let range = try LanguageServerProtocol.Range.decode(data["range"]) 840 | let command = try? Command.decode(data["command"]) 841 | let data = data["data"] 842 | return CodeLens( 843 | range: range, 844 | command: command, 845 | data: data 846 | ) 847 | } 848 | } 849 | 850 | extension DocumentLinkParams: Decodable { 851 | public typealias EncodableType = JSValue 852 | 853 | public static func decode(_ data: JSValue?) throws -> DocumentLinkParams { 854 | let textDocument = try TextDocumentIdentifier.decode(data["textDocument"]) 855 | return DocumentLinkParams(textDocument: textDocument) 856 | } 857 | } 858 | 859 | extension DocumentLink: Decodable { 860 | public typealias EncodableType = JSValue 861 | 862 | public static func decode(_ data: JSValue?) throws -> DocumentLink { 863 | return DocumentLink( 864 | range: try LanguageServerProtocol.Range.decode(data["range"]), 865 | target: try String.decode(data["target"]) 866 | ) 867 | } 868 | } 869 | 870 | extension RenameParams: Decodable { 871 | public typealias EncodableType = JSValue 872 | 873 | public static func decode(_ data: JSValue?) throws -> RenameParams { 874 | return RenameParams( 875 | textDocument: try TextDocumentIdentifier.decode(data["textDocument"]), 876 | position: try Position.decode(data["position"]), 877 | newName: try String.decode(data["newName"]) 878 | ) 879 | } 880 | } 881 | 882 | extension MessageType: Decodable { 883 | public typealias EncodableType = JSValue 884 | 885 | public static func decode(_ data: JSValue?) throws -> MessageType { 886 | guard let value = data.integer else { 887 | throw "The message type must be a number." 888 | } 889 | 890 | switch value { 891 | case 1: return .error 892 | case 2: return .warning 893 | case 3: return .info 894 | case 4: return .log 895 | default: throw "The value '\(value)' is not a supported message type." 896 | } 897 | } 898 | } 899 | 900 | extension MessageActionItem: Decodable { 901 | public typealias EncodableType = JSValue 902 | 903 | public static func decode(_ data: JSValue?) throws -> MessageActionItem { 904 | return MessageActionItem(title: try String.decode(data["title"])) 905 | } 906 | } 907 | 908 | extension DynamicRegistrationCapability: Decodable { 909 | public typealias EncodableType = JSValue 910 | 911 | public static func decode(_ data: JSValue?) throws -> DynamicRegistrationCapability { 912 | return DynamicRegistrationCapability( 913 | dynamicRegistration: try? Bool.decode(data["dynamicRegistration"])) 914 | } 915 | } 916 | 917 | extension CompletionCapability: Decodable { 918 | public typealias EncodableType = JSValue 919 | 920 | public static func decode(_ data: JSValue?) throws -> CompletionCapability { 921 | return CompletionCapability( 922 | dynamicRegistration: try? Bool.decode(data["dynamicRegistration"]), 923 | completionItem: try? CompletionItemCapability.decode(data["completionItem"])) 924 | } 925 | } 926 | 927 | extension CompletionItemCapability: Decodable { 928 | public typealias EncodableType = JSValue 929 | 930 | public static func decode(_ data: JSValue?) throws -> CompletionItemCapability { 931 | return CompletionItemCapability(snippetSupport: try? Bool.decode(data["snippetSupport"])) 932 | } 933 | } 934 | 935 | extension SynchronizationCapability: Decodable { 936 | public typealias EncodableType = JSValue 937 | 938 | public static func decode(_ data: JSValue?) throws -> SynchronizationCapability { 939 | return SynchronizationCapability( 940 | dynamicRegistration: try? Bool.decode(data["dynamicRegistration"]), 941 | willSave: try? Bool.decode(data["willSave"]), 942 | willSaveWaitUntil: try? Bool.decode(data["willSaveWaitUntil"]), 943 | didSave: try? Bool.decode(data["didSave"])) 944 | } 945 | } 946 | 947 | extension DocumentChangesCapability: Decodable { 948 | public typealias EncodableType = JSValue 949 | 950 | public static func decode(_ data: JSValue?) throws -> DocumentChangesCapability { 951 | return DocumentChangesCapability(documentChanges: try? Bool.decode(data["documentChanges"])) 952 | } 953 | } 954 | 955 | extension WorkspaceClientCapabilities: Decodable { 956 | public typealias EncodableType = JSValue 957 | 958 | public static func decode(_ data: JSValue?) throws -> WorkspaceClientCapabilities { 959 | return WorkspaceClientCapabilities( 960 | applyEdit: try? Bool.decode(data["applyEdit"]), 961 | workspaceEdit: try? DocumentChangesCapability.decode(data["workspaceEdit"]), 962 | didChangeConfiguration: try? DynamicRegistrationCapability.decode(data["didChangeConfiguration"]), 963 | didChangeWatchedFiles: try? DynamicRegistrationCapability.decode(data["didChangeWatchedFiles"]), 964 | symbol: try? DynamicRegistrationCapability.decode(data["symbol"]), 965 | executeCommand: try? DynamicRegistrationCapability.decode(data["executeCommand"])) 966 | } 967 | } 968 | -------------------------------------------------------------------------------- /Sources/JsonRpcProtocol/Encoders.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | import LanguageServerProtocol 8 | 9 | #if os(macOS) 10 | import os.log 11 | #endif 12 | 13 | @available(macOS 10.12, *) 14 | fileprivate let log = OSLog(subsystem: "com.kiadstudios.jsonrpcprotocol", category: "Encodable") 15 | 16 | extension JSValue: AnyEncodable { 17 | public func encode() -> Any { 18 | return self 19 | } 20 | } 21 | 22 | extension String: Encodable { 23 | public func encode() -> JSValue { 24 | return JSValue(self) 25 | } 26 | } 27 | 28 | extension Double: Encodable { 29 | public func encode() -> JSValue { 30 | return JSValue(self) 31 | } 32 | } 33 | 34 | extension Bool: Encodable { 35 | public func encode() -> JSValue { 36 | return JSValue(self) 37 | } 38 | } 39 | 40 | extension Int: Encodable { 41 | public func encode() -> JSValue { 42 | return JSValue(Double(self)) 43 | } 44 | } 45 | 46 | extension TextDocumentSync: Encodable { 47 | public func encode() -> JSValue { 48 | switch self { 49 | case .options(let options): return options.encode() 50 | case .kind(let kind): return kind.encode() 51 | } 52 | } 53 | } 54 | 55 | extension CodeLens: Encodable { 56 | public func encode() -> JSValue { 57 | var json: JSValue = [:] 58 | json["range"] = range.encode() 59 | if let command = command { 60 | json["command"] = command.encode() 61 | } 62 | return json 63 | } 64 | } 65 | 66 | extension CodeLensOptions: Encodable { 67 | public func encode() -> JSValue { 68 | var json: JSValue = [:] 69 | if let resolveProvider = resolveProvider { 70 | json["resolveProvider"] = resolveProvider.encode() 71 | } 72 | return json 73 | } 74 | } 75 | 76 | extension Command: Encodable { 77 | public func encode() -> JSValue { 78 | var json: JSValue = [:] 79 | json["title"] = title.encode() 80 | json["command"] = command.encode() 81 | if let arguments = arguments { 82 | json["arguments"] = arguments.encode() as? JSValue 83 | } 84 | return json 85 | } 86 | } 87 | 88 | extension CompletionOptions: Encodable { 89 | public func encode() -> JSValue { 90 | var json: JSValue = [:] 91 | if let resolveProvider = resolveProvider { 92 | json["resolveProvider"] = resolveProvider.encode() 93 | } 94 | if let triggerCharacters = triggerCharacters { 95 | let values: [JSValue] = triggerCharacters.map { $0.encode() } 96 | json["triggerCharacters"] = JSValue(values) 97 | } 98 | return json 99 | } 100 | } 101 | 102 | extension DocumentHighlight: Encodable { 103 | public func encode() -> JSValue { 104 | var json: JSValue = [:] 105 | json["range"] = range.encode() 106 | if let kind = kind { 107 | json["kind"] = kind.encode() 108 | } 109 | return json 110 | } 111 | } 112 | 113 | extension DocumentLink: Encodable { 114 | public func encode() -> JSValue { 115 | var json: JSValue = [:] 116 | json["range"] = range.encode() 117 | if let target = target { 118 | json["target"] = target.encode() 119 | } 120 | return json 121 | } 122 | } 123 | 124 | extension DocumentLinkOptions: Encodable { 125 | public func encode() -> JSValue { 126 | var json: JSValue = [:] 127 | if let resolveProvider = resolveProvider { 128 | json["resolveProvider"] = resolveProvider.encode() 129 | } 130 | return json 131 | } 132 | } 133 | 134 | extension DocumentOnTypeFormattingOptions: Encodable { 135 | public func encode() -> JSValue { 136 | var json: JSValue = [:] 137 | json["firstTriggerCharacter"] = firstTriggerCharacter.encode() 138 | if let moreTriggerCharacter = moreTriggerCharacter { 139 | json["moreTriggerCharacter"] = JSValue(moreTriggerCharacter.map { $0.encode() }) 140 | } 141 | return json 142 | } 143 | } 144 | 145 | extension ExecuteCommandOptions: Encodable { 146 | public func encode() -> JSValue { 147 | var json: JSValue = [:] 148 | if let commands = commands { 149 | json["commands"] = JSValue(commands.map { $0.encode() }) 150 | } 151 | 152 | return json 153 | } 154 | } 155 | 156 | extension Hover: Encodable { 157 | public func encode() -> JSValue { 158 | var json: JSValue = [:] 159 | json["contents"] = JSValue(contents.map { $0.encode() }) 160 | if let range = range { 161 | json["range"] = range.encode() 162 | } 163 | return json 164 | } 165 | } 166 | 167 | extension InitializeResult: Encodable { 168 | public func encode() -> JSValue { 169 | var json: JSValue = [:] 170 | json["capabilities"] = capabilities.encode() 171 | return json 172 | } 173 | } 174 | 175 | extension Location: Encodable { 176 | public func encode() -> JSValue { 177 | var json: JSValue = [:] 178 | json["uri"] = uri.encode() 179 | json["range"] = range.encode() 180 | return json 181 | } 182 | } 183 | 184 | extension Position: Encodable { 185 | public func encode() -> JSValue { 186 | var json: JSValue = [:] 187 | json["line"] = line.encode() 188 | json["character"] = character.encode() 189 | return json 190 | } 191 | } 192 | 193 | extension ServerCapabilities: Encodable { 194 | public func encode() -> JSValue { 195 | var json: JSValue = [:] 196 | if let textDocumentSync = textDocumentSync { 197 | json["textDocumentSync"] = textDocumentSync.encode() 198 | } 199 | if let hoverProvider = hoverProvider { 200 | json["hoverProvider"] = hoverProvider.encode() 201 | } 202 | if let completionProvider = completionProvider { 203 | json["completionProvider"] = completionProvider.encode() 204 | } 205 | if let signatureHelpProvider = signatureHelpProvider { 206 | json["signatureHelpProvider"] = signatureHelpProvider.encode() 207 | } 208 | if let definitionProvider = definitionProvider { 209 | json["definitionProvider"] = definitionProvider.encode() 210 | } 211 | if let referencesProvider = referencesProvider { 212 | json["referencesProvider"] = referencesProvider.encode() 213 | } 214 | if let documentHighlightProvider = documentHighlightProvider { 215 | json["documentHighlightProvider"] = documentHighlightProvider.encode() 216 | } 217 | if let documentSymbolProvider = documentSymbolProvider { 218 | json["documentSymbolProvider"] = documentSymbolProvider.encode() 219 | } 220 | if let workspaceSymbolProvider = workspaceSymbolProvider { 221 | json["workspaceSymbolProvider"] = workspaceSymbolProvider.encode() 222 | } 223 | if let codeActionProvider = codeActionProvider { 224 | json["codeActionProvider"] = codeActionProvider.encode() 225 | } 226 | if let codeLensProvider = codeLensProvider { 227 | json["codeLensProvider"] = codeLensProvider.encode() 228 | } 229 | if let documentFormattingProvider = documentFormattingProvider { 230 | json["documentFormattingProvider"] = documentFormattingProvider.encode() 231 | } 232 | if let documentRangeFormattingProvider = documentRangeFormattingProvider { 233 | json["documentRangeFormattingProvider"] = documentRangeFormattingProvider.encode() 234 | } 235 | if let documentOnTypeFormattingProvider = documentOnTypeFormattingProvider { 236 | json["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.encode() 237 | } 238 | if let renameProvider = renameProvider { 239 | json["renameProvider"] = renameProvider.encode() 240 | } 241 | if let documentLinkProvider = documentLinkProvider { 242 | json["documentLinkProvider"] = documentLinkProvider.encode() 243 | } 244 | if let executeCommandProvider = executeCommandProvider { 245 | json["executeCommandProvider"] = executeCommandProvider.encode() 246 | } 247 | return json 248 | } 249 | } 250 | 251 | extension ShowMessageRequestParams: Encodable { 252 | public func encode() -> JSValue { 253 | var json: JSValue = [:] 254 | json["type"] = type.encode() 255 | json["message"] = message.encode() 256 | if let actions = actions { 257 | json["actions"] = JSValue(actions.map { $0.encode() }) 258 | } 259 | return json 260 | } 261 | } 262 | 263 | extension SignatureHelp: Encodable { 264 | public func encode() -> JSValue { 265 | var json: JSValue = [:] 266 | json["signatures"] = JSValue(signatures.map { $0.encode() }) 267 | if let activeSignature = activeSignature { 268 | json["activeSignature"] = activeSignature.encode() 269 | } 270 | if let activeParameter = activeParameter { 271 | json["activeParameter"] = activeParameter.encode() 272 | } 273 | return json 274 | } 275 | } 276 | 277 | extension SignatureHelpOptions: Encodable { 278 | public func encode() -> JSValue { 279 | var json: JSValue = [:] 280 | if let triggerCharacters = triggerCharacters { 281 | json["triggerCharacters"] = JSValue(triggerCharacters.map { $0.encode() }) 282 | } 283 | return json 284 | } 285 | } 286 | 287 | extension SymbolInformation: Encodable { 288 | public func encode() -> JSValue { 289 | var json: JSValue = [:] 290 | json["name"] = name.encode() 291 | json["kind"] = kind.encode() 292 | json["location"] = location.encode() 293 | if let containerName = containerName { 294 | json["containerName"] = containerName.encode() 295 | } 296 | return json 297 | } 298 | } 299 | 300 | extension TextDocumentSyncOptions: Encodable { 301 | public func encode() -> JSValue { 302 | var json: JSValue = [:] 303 | if let openClose = openClose { 304 | json["openClose"] = openClose.encode() 305 | } 306 | if let change = change { 307 | json["change"] = change.encode() 308 | } 309 | if let willSave = willSave { 310 | json["willSave"] = willSave.encode() 311 | } 312 | if let willSaveWaitUntil = willSaveWaitUntil { 313 | json["willSaveWaitUntil"] = willSaveWaitUntil.encode() 314 | } 315 | if let save = save { 316 | json["save"] = save.encode() 317 | } 318 | return json 319 | } 320 | } 321 | 322 | extension TextEdit: Encodable { 323 | public func encode() -> JSValue { 324 | var json: JSValue = [:] 325 | json["range"] = range.encode() 326 | json["newText"] = newText.encode() 327 | return json 328 | } 329 | } 330 | 331 | 332 | 333 | extension LanguageServerProtocol.Range: Encodable { 334 | public func encode() -> JSValue { 335 | var json: JSValue = [:] 336 | json["start"] = start.encode() 337 | json["end"] = end.encode() 338 | return json 339 | } 340 | } 341 | 342 | extension RequestId: Encodable { 343 | public func encode() -> JSValue { 344 | switch self { 345 | case let .number(value): return JSValue(Double(value)) 346 | case let .string(value): return JSValue(value) 347 | } 348 | } 349 | } 350 | 351 | extension DocumentHighlightKind: Encodable { 352 | public func encode() -> JSValue { 353 | return JSValue(Double(self.rawValue)) 354 | } 355 | } 356 | 357 | extension MarkedString: Encodable { 358 | public func encode() -> JSValue { 359 | switch self { 360 | case let .string(value): return JSValue(value) 361 | case let .code(language, value): 362 | return [ 363 | "language": JSValue(language), 364 | "value": JSValue(value) 365 | ] 366 | } 367 | } 368 | } 369 | 370 | extension MessageActionItem: Encodable { 371 | public func encode() -> JSValue { 372 | return ["title": JSValue(self.title)] 373 | } 374 | } 375 | 376 | extension MessageType: Encodable { 377 | public func encode() -> JSValue { 378 | return JSValue(Double(self.rawValue)) 379 | } 380 | } 381 | 382 | extension SignatureInformation: Encodable { 383 | public func encode() -> JSValue { 384 | var json: JSValue = [:] 385 | json["label"] = label.encode() 386 | if let doc = documentation { 387 | json["documentation"] = doc.encode() 388 | } 389 | if let params = parameters { 390 | json["parameters"] = JSValue(params.map { $0.encode() }) 391 | } 392 | return json 393 | } 394 | } 395 | 396 | extension SymbolKind: Encodable { 397 | public func encode() -> JSValue { 398 | return JSValue(Double(self.rawValue)) 399 | } 400 | } 401 | 402 | extension TextDocumentSyncKind: Encodable { 403 | public func encode() -> JSValue { 404 | return JSValue(Double(self.rawValue)) 405 | } 406 | } 407 | 408 | extension CompletionItemKind: Encodable { 409 | public func encode() -> JSValue { 410 | return JSValue(Double(self.rawValue)) 411 | } 412 | } 413 | 414 | extension SaveOptions: Encodable { 415 | public func encode() -> JSValue { 416 | var json: JSValue = [:] 417 | if let include = includeText { 418 | json["includeText"] = include.encode() 419 | } 420 | return json 421 | } 422 | } 423 | 424 | extension CompletionListResult: Encodable { 425 | public func encode() -> JSValue { 426 | switch self { 427 | case let .completionItems(items): return JSValue(items.map { $0.encode() }) 428 | case let .completionList(list): return list.encode() 429 | } 430 | } 431 | } 432 | 433 | extension ParameterInformation: Encodable { 434 | public func encode() -> JSValue { 435 | var json: JSValue = [:] 436 | json["label"] = label.encode() 437 | if let doc = documentation { 438 | json["documentation"] = doc.encode() 439 | } 440 | return json 441 | } 442 | } 443 | 444 | extension DidChangeConfigurationParams: Encodable { 445 | public func encode() -> JSValue { 446 | return settings.encode() as! JSValue 447 | } 448 | } 449 | 450 | 451 | extension CompletionItem: Encodable { 452 | public func encode() -> JSValue { 453 | var json: JSValue = [:] 454 | json["label"] = label.encode() 455 | 456 | if let kind = kind { 457 | json["kind"] = kind.encode() 458 | } 459 | if let detail = detail { 460 | json["detail"] = detail.encode() 461 | } 462 | if let documentation = documentation { 463 | json["documentation"] = documentation.encode() 464 | } 465 | if let sortText = sortText { 466 | json["sortText"] = sortText.encode() 467 | } 468 | if let filterText = filterText { 469 | json["filterText"] = filterText.encode() 470 | } 471 | if let insertText = insertText { 472 | json["insertText"] = insertText.encode() 473 | } 474 | if let insertTextFormat = insertTextFormat { 475 | json["insertTextFormat"] = insertTextFormat.encode() 476 | } 477 | if let textEdit = textEdit { 478 | json["textEdit"] = textEdit.encode() 479 | } 480 | if let additionalTextEdits = additionalTextEdits { 481 | json["additionalTextEdits"] = JSValue(additionalTextEdits.map { $0.encode() }) 482 | } 483 | if let command = command { 484 | json["command"] = command.encode() 485 | } 486 | if let data = data { 487 | json["data"] = data.encode() as? JSValue 488 | } 489 | 490 | return json 491 | } 492 | } 493 | 494 | extension CompletionList: Encodable { 495 | public func encode() -> JSValue { 496 | var json: JSValue = [:] 497 | json["isIncomplete"] = isIncomplete.encode() 498 | json["items"] = JSValue(items.map { $0.encode() }) 499 | return json 500 | } 501 | } 502 | 503 | extension InsertTextFormat: Encodable { 504 | public func encode() -> JSValue { 505 | return JSValue(Double(self.rawValue)) 506 | } 507 | } 508 | 509 | extension WorkspaceEdit: Encodable { 510 | public func encode() -> JSValue { 511 | switch self { 512 | case let .changes(changes): 513 | var json: JSValue = [:] 514 | for (key, value) in changes { 515 | json[key] = JSValue(value.map { $0.encode() }) 516 | } 517 | return json 518 | case let .documentChanges(changes): return JSValue(changes.map { $0.encode() }) 519 | } 520 | } 521 | } 522 | 523 | extension TextDocumentEdit: Encodable { 524 | public func encode() -> JSValue { 525 | var json: JSValue = [:] 526 | json["textDocument"] = textDocument.encode() 527 | json["edits"] = JSValue(edits.map { $0.encode() }) 528 | return json 529 | } 530 | } 531 | 532 | extension VersionedTextDocumentIdentifier: Encodable { 533 | public func encode() -> JSValue { 534 | var json: JSValue = [:] 535 | json["uri"] = uri.encode() 536 | json["version"] = version.encode() 537 | return json 538 | } 539 | } 540 | 541 | extension Diagnostic: Encodable { 542 | public func encode() -> JSValue { 543 | var json: JSValue = [:] 544 | json["range"] = range.encode() 545 | json["message"] = message.encode() 546 | if let severity = severity { 547 | json["severity"] = severity.encode() 548 | } 549 | if let code = code { 550 | json["code"] = code.encode() 551 | } 552 | if let source = source { 553 | json["source"] = source.encode() 554 | } 555 | return json 556 | } 557 | } 558 | 559 | extension DiagnosticCode: Encodable { 560 | public func encode() -> JSValue { 561 | switch self { 562 | case let .number(value): return value.encode() 563 | case let .string(value): return value.encode() 564 | } 565 | } 566 | } 567 | 568 | extension DiagnosticSeverity: Encodable { 569 | public func encode() -> JSValue { 570 | return JSValue(Double(self.rawValue)) 571 | } 572 | } 573 | 574 | extension Registration: Encodable { 575 | public func encode() -> JSValue { 576 | var json: JSValue = [:] 577 | json["id"] = id.encode() 578 | json["method"] = method.encode() 579 | if let options = registerOptions?.encode() { 580 | json["registerOptions"] = options as? JSValue 581 | } 582 | return json 583 | } 584 | } 585 | 586 | extension ShowMessageParams: Encodable { 587 | public func encode() -> JSValue { 588 | var json: JSValue = [:] 589 | json["type"] = type.encode() 590 | json["message"] = message.encode() 591 | return json 592 | } 593 | } 594 | 595 | extension LogMessageParams: Encodable { 596 | public func encode() -> JSValue { 597 | var json: JSValue = [:] 598 | json["type"] = type.encode() 599 | json["message"] = message.encode() 600 | return json 601 | } 602 | } 603 | 604 | extension PublishDiagnosticsParams: Encodable { 605 | public func encode() -> JSValue { 606 | var json: JSValue = [:] 607 | json["uri"] = uri.encode() 608 | json["diagnostics"] = JSValue(diagnostics.map { $0.encode() }) 609 | return json 610 | } 611 | } 612 | 613 | extension LanguageServerResponse: Encodable { 614 | private func encode(_ requestId: RequestId, _ encodables: [EncodingType]?) -> JSValue where EncodingType: Encodable { 615 | var result: JSValue = nil 616 | if let e = encodables { 617 | result = JSValue(e.map { $0.encode() as! JSValue }) 618 | } 619 | 620 | return [ 621 | "jsonrpc": "2.0", 622 | "id": requestId.encode(), 623 | "result": result] 624 | } 625 | 626 | private func encode(_ requestId: RequestId, _ encodable: EncodingType?) -> JSValue where EncodingType: Encodable { 627 | return [ 628 | "jsonrpc": "2.0", 629 | "id": requestId.encode(), 630 | "result": encodable?.encode() as! JSValue? ?? .null] 631 | } 632 | 633 | private func encode(_ requestId: RequestId) -> JSValue { 634 | return [ 635 | "jsonrpc": "2.0", 636 | "id": requestId.encode(), 637 | "result": nil] 638 | } 639 | 640 | private func encode(_ method: String, _ encodable: EncodingType) -> JSValue where EncodingType: Encodable { 641 | return [ 642 | "jsonrpc": "2.0", 643 | "method": JSValue(method), 644 | "params": encodable.encode() as! JSValue] 645 | } 646 | 647 | public func encode() -> JSValue { 648 | switch self { 649 | case let .initialize(requestId, result): return encode(requestId, result) 650 | case let .shutdown(requestId): return encode(requestId) 651 | 652 | case let .showMessageRequest(requestId, result): return encode(requestId, result) 653 | 654 | case let .clientRegisterCapability(requestId): return encode(requestId) 655 | case let .clientUnregisterCapability(requestId): return encode(requestId) 656 | 657 | case let .workspaceSymbol(requestId, result): return encode(requestId, result) 658 | case let .workspaceExecuteCommand(requestId): return encode(requestId) 659 | 660 | case let .completionItemResolve(requestId, result): return encode(requestId, result) 661 | case let .codeLensResolve(requestId, result): return encode(requestId, result) 662 | case let .documentLinkResolve(requestId, result): return encode(requestId, result) 663 | 664 | case let .textDocumentWillSaveWaitUntil(requestId, result): return encode(requestId, result) 665 | case let .textDocumentCompletion(requestId, result): return encode(requestId, result) 666 | case let .textDocumentHover(requestId, result): return encode(requestId, result) 667 | case let .textDocumentSignatureHelp(requestId, result): return encode(requestId, result) 668 | case let .textDocumentReferences(requestId, result): return encode(requestId, result) 669 | case let .textDocumentDocumentHighlight(requestId, result): return encode(requestId, result) 670 | case let .textDocumentDocumentSymbol(requestId, result): return encode(requestId, result) 671 | case let .textDocumentFormatting(requestId, result): return encode(requestId, result) 672 | case let .textDocumentRangeFormatting(requestId, result): return encode(requestId, result) 673 | case let .textDocumentOnTypeFormatting(requestId, result): return encode(requestId, result) 674 | case let .textDocumentDefinition(requestId, result): return encode(requestId, result) 675 | case let .textDocumentCodeAction(requestId, result): return encode(requestId, result) 676 | case let .textDocumentCodeLens(requestId, result): return encode(requestId, result) 677 | case let .textDocumentDocumentLink(requestId, result): return encode(requestId, result) 678 | case let .textDocumentRename(requestId, result): return encode(requestId, result) 679 | 680 | case let .windowShowMessage(params): return encode("window/showMessage", params) 681 | case let .windowLogMessage(params): return encode("window/logMessage", params) 682 | case .telemetryEvent(_): 683 | var json: JSValue = [:] 684 | json["message"] = "telemetry/event is not currently supported." 685 | return json 686 | case let .textDocumentPublishDiagnostics(params): return encode("textDocument/publishDiagnostics", params) 687 | } 688 | } 689 | } 690 | -------------------------------------------------------------------------------- /Sources/JsonRpcProtocol/JsonRpcProtocol.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | import LanguageServerProtocol 8 | import Foundation 9 | 10 | #if os(macOS) 11 | import os.log 12 | #endif 13 | 14 | @available(macOS 10.12, *) 15 | fileprivate let log = OSLog(subsystem: "com.kiadstudios.jsonrpcprotocol", category: "JsonRpcProtocol") 16 | 17 | /// This provides the complete implementation necessary to translate an incoming message to a 18 | /// `LanguageServiceCommand`. 19 | public final class JsonRpcProtocol: MessageProtocol { 20 | /// The registration table for all of the commands that can be handled via this protocol. 21 | public var protocols: [String:(JSValue) throws -> LanguageServerCommand] = [:] 22 | 23 | /// Creates a new instance of the `LanguageServerProtocol`. 24 | public init() { 25 | protocols["initialize"] = general(initialize:) 26 | protocols["initialized"] = general(initialized:) 27 | protocols["shutdown"] = general(shutdown:) 28 | protocols["exit"] = general(exit:) 29 | protocols["$/cancelRequest"] = general(cancelRequest:) 30 | 31 | protocols["window/showMessageRequest"] = window(showMessageRequest:) 32 | 33 | protocols["client/registerCapability"] = client(registerCapability:) 34 | protocols["client/unregisterCapability"] = client(unregisterCapability:) 35 | 36 | protocols["workspace/didChangeConfiguration"] = workspace(didChangeConfiguration:) 37 | protocols["workspace/didChangeWatchedFiles"] = workspace(didChangeWatchedFiles:) 38 | protocols["workspace/symbol"] = workspace(symbol:) 39 | protocols["workspace/executeCommand"] = workspace(executeCommand:) 40 | protocols["workspace/applyEdit"] = workspace(applyEdit:) 41 | 42 | protocols["textDocument/didOpen"] = textDocument(didOpen:) 43 | protocols["textDocument/didChange"] = textDocument(didChange:) 44 | protocols["textDocument/willSave"] = textDocument(willSave:) 45 | protocols["textDocument/willSaveWaitUntil"] = textDocument(willSaveWaitUntil:) 46 | protocols["textDocument/didSave"] = textDocument(didSave:) 47 | protocols["textDocument/didClose"] = textDocument(didClose:) 48 | protocols["textDocument/completion"] = textDocument(completion:) 49 | protocols["completionItem/resolve"] = completionItem(resolve:) 50 | protocols["textDocument/hover"] = textDocument(hover:) 51 | protocols["textDocument/signatureHelp"] = textDocument(signatureHelp:) 52 | protocols["textDocument/references"] = textDocument(references:) 53 | protocols["textDocument/documentHighlight"] = textDocument(documentHighlight:) 54 | protocols["textDocument/documentSymbol"] = textDocument(documentSymbol:) 55 | protocols["textDocument/formatting"] = textDocument(formatting:) 56 | protocols["textDocument/rangeFormatting"] = textDocument(rangeFormatting:) 57 | protocols["textDocument/onTypeFormatting"] = textDocument(onTypeFormatting:) 58 | protocols["textDocument/definition"] = textDocument(definition:) 59 | protocols["textDocument/codeAction"] = textDocument(codeAction:) 60 | protocols["textDocument/codeLens"] = textDocument(codeLens:) 61 | protocols["codeLens/resolve"] = codeLens(resolve:) 62 | protocols["textDocument/documentLink"] = textDocument(documentLink:) 63 | protocols["documentLink/resolve"] = documentLink(resolve:) 64 | protocols["textDocument/rename"] = textDocument(rename:) 65 | } 66 | 67 | /// This is used to convert the raw incoming message to a `LanguageServerCommand`. The internals 68 | /// handle the JSON-RPC mechanism, but that doesn't need to be exposed. 69 | public func translate(message: Message) throws-> LanguageServerCommand { 70 | guard let json = try? JSValue.parse(message.content) else { 71 | throw "unable to parse the incoming message" 72 | } 73 | 74 | if json["jsonrpc"] != "2.0" { 75 | throw "The only 'jsonrpc' value supported is '2.0'." 76 | } 77 | 78 | guard let method = json["method"].string else { 79 | throw "A message is required to have a `method` parameter." 80 | } 81 | 82 | if let parser = protocols[method] { 83 | return try parser(json) 84 | } 85 | else { 86 | throw "unhandled method `\(method)`" 87 | } 88 | } 89 | 90 | /// Translates the response into a raw `MessageData`. This function can throw, providing detailed 91 | /// error information about why the transformation could not be done. 92 | public func translate(response: LanguageServerResponse) throws -> Message { 93 | let json = response.encode().stringify(nil) 94 | guard let data = json.data(using: .utf8) else { 95 | throw "unable to convert JSON into data stream" 96 | } 97 | 98 | let fields = [ 99 | MessageHeader.contentLengthKey: "\(data.count)", 100 | MessageHeader.contentTypeKey: MessageHeader.defaultContentType] 101 | 102 | return Message( 103 | header: MessageHeader(headerFields: fields), 104 | content: [UInt8](data) 105 | ) 106 | } 107 | 108 | private func general(initialize json: JSValue) throws -> LanguageServerCommand { 109 | return .initialize( 110 | requestId: try RequestId.decode(json["id"]), 111 | params: try InitializeParams.decode(json["params"])) 112 | } 113 | 114 | private func general(initialized json: JSValue) throws -> LanguageServerCommand { 115 | return .initialized 116 | } 117 | 118 | private func general(shutdown json: JSValue) throws -> LanguageServerCommand { 119 | return .shutdown(requestId: try RequestId.decode(json["id"])) 120 | } 121 | 122 | private func general(exit json: JSValue) throws -> LanguageServerCommand { 123 | return .exit 124 | } 125 | 126 | private func general(cancelRequest json: JSValue) throws -> LanguageServerCommand { 127 | return .cancelRequest(params: try CancelParams.decode(json["params"])) 128 | } 129 | 130 | private func window(showMessageRequest json: JSValue) throws -> LanguageServerCommand { 131 | return .windowShowMessageRequest( 132 | requestId: try RequestId.decode(json["id"]), 133 | params: try ShowMessageRequestParams.decode(json["params"])) 134 | } 135 | 136 | private func client(registerCapability json: JSValue) throws -> LanguageServerCommand { 137 | return .clientRegisterCapability( 138 | requestId: try RequestId.decode(json["id"]), 139 | params: try RegistrationParams.decode(json["params"])) 140 | } 141 | 142 | private func client(unregisterCapability json: JSValue) throws -> LanguageServerCommand { 143 | return .clientUnregisterCapability( 144 | requestId: try RequestId.decode(json["id"]), 145 | params: try UnregistrationParams.decode(json["params"])) 146 | } 147 | 148 | private func workspace(didChangeConfiguration json: JSValue) throws -> LanguageServerCommand { 149 | return .workspaceDidChangeConfiguration(params: try DidChangeConfigurationParams.decode(json["params"])) 150 | } 151 | 152 | private func workspace(didChangeWatchedFiles json: JSValue) throws -> LanguageServerCommand { 153 | return .workspaceDidChangeWatchedFiles(params: try DidChangeWatchedFilesParams.decode(json["params"])) 154 | } 155 | 156 | private func workspace(symbol json: JSValue) throws -> LanguageServerCommand { 157 | return .workspaceSymbol( 158 | requestId: try RequestId.decode(json["id"]), 159 | params: try WorkspaceSymbolParams.decode(json["params"])) 160 | } 161 | 162 | private func workspace(executeCommand json: JSValue) throws -> LanguageServerCommand { 163 | return .workspaceExecuteCommand( 164 | requestId: try RequestId.decode(json["id"]), 165 | params: try ExecuteCommandParams.decode(json["params"])) 166 | } 167 | 168 | private func workspace(applyEdit json: JSValue) throws -> LanguageServerCommand { 169 | return .workspaceApplyEdit( 170 | requestId: try RequestId.decode(json["id"]), 171 | params: try ApplyWorkspaceEditParams.decode(json["params"])) 172 | } 173 | 174 | private func textDocument(didOpen json: JSValue) throws -> LanguageServerCommand { 175 | return .textDocumentDidOpen(params: try DidOpenTextDocumentParams.decode(json["params"])) 176 | } 177 | 178 | private func textDocument(didChange json: JSValue) throws -> LanguageServerCommand { 179 | return .textDocumentDidChange(params: try DidChangeTextDocumentParams.decode(json["params"])) 180 | } 181 | 182 | private func textDocument(willSave json: JSValue) throws -> LanguageServerCommand { 183 | return .textDocumentWillSave(params: try WillSaveTextDocumentParams.decode(json["params"])) 184 | } 185 | 186 | private func textDocument(willSaveWaitUntil json: JSValue) throws -> LanguageServerCommand { 187 | return .textDocumentWillSaveWaitUntil( 188 | requestId: try RequestId.decode(json["id"]), 189 | params: try WillSaveTextDocumentParams.decode(json["params"])) 190 | } 191 | 192 | private func textDocument(didSave json: JSValue) throws -> LanguageServerCommand { 193 | return .textDocumentDidSave(params: try DidSaveTextDocumentParams.decode(json["params"])) 194 | } 195 | 196 | private func textDocument(didClose json: JSValue) throws -> LanguageServerCommand { 197 | return .textDocumentDidClose(params: try DidCloseTextDocumentParams.decode(json["params"])) 198 | } 199 | 200 | private func textDocument(completion json: JSValue) throws -> LanguageServerCommand { 201 | return .textDocumentCompletion( 202 | requestId: try RequestId.decode(json["id"]), 203 | params: try TextDocumentPositionParams.decode(json["params"])) 204 | } 205 | 206 | private func completionItem(resolve json: JSValue) throws -> LanguageServerCommand { 207 | return .completionItemResolve( 208 | requestId: try RequestId.decode(json["id"]), 209 | params: try CompletionItem.decode(json["params"])) 210 | } 211 | 212 | private func textDocument(hover json: JSValue) throws -> LanguageServerCommand { 213 | return .textDocumentHover( 214 | requestId: try RequestId.decode(json["id"]), 215 | params: try TextDocumentPositionParams.decode(json["params"])) 216 | } 217 | 218 | private func textDocument(signatureHelp json: JSValue) throws -> LanguageServerCommand { 219 | return .textDocumentSignatureHelp( 220 | requestId: try RequestId.decode(json["id"]), 221 | params: try TextDocumentPositionParams.decode(json["params"])) 222 | } 223 | 224 | private func textDocument(references json: JSValue) throws -> LanguageServerCommand { 225 | return .textDocumentReferences( 226 | requestId: try RequestId.decode(json["id"]), 227 | params: try ReferenceParams.decode(json["params"])) 228 | } 229 | 230 | private func textDocument(documentHighlight json: JSValue) throws -> LanguageServerCommand { 231 | return .textDocumentDocumentHighlight( 232 | requestId: try RequestId.decode(json["id"]), 233 | params: try TextDocumentPositionParams.decode(json["params"])) 234 | } 235 | 236 | private func textDocument(documentSymbol json: JSValue) throws -> LanguageServerCommand { 237 | return .textDocumentDocumentSymbol( 238 | requestId: try RequestId.decode(json["id"]), 239 | params: try DocumentSymbolParams.decode(json["params"])) 240 | } 241 | 242 | private func textDocument(formatting json: JSValue) throws -> LanguageServerCommand { 243 | return .textDocumentFormatting( 244 | requestId: try RequestId.decode(json["id"]), 245 | params: try DocumentFormattingParams.decode(json["params"])) 246 | } 247 | 248 | private func textDocument(rangeFormatting json: JSValue) throws -> LanguageServerCommand { 249 | return .textDocumentRangeFormatting( 250 | requestId: try RequestId.decode(json["id"]), 251 | params: try DocumentRangeFormattingParams.decode(json["params"])) 252 | } 253 | 254 | private func textDocument(onTypeFormatting json: JSValue) throws -> LanguageServerCommand { 255 | return .textDocumentOnTypeFormatting( 256 | requestId: try RequestId.decode(json["id"]), 257 | params: try DocumentOnTypeFormattingParams.decode(json["params"])) 258 | } 259 | 260 | private func textDocument(definition json: JSValue) throws -> LanguageServerCommand { 261 | return .textDocumentDefinition( 262 | requestId: try RequestId.decode(json["id"]), 263 | params: try TextDocumentPositionParams.decode(json["params"])) 264 | } 265 | 266 | private func textDocument(codeAction json: JSValue) throws -> LanguageServerCommand { 267 | return .textDocumentCodeAction( 268 | requestId: try RequestId.decode(json["id"]), 269 | params: try CodeActionParams.decode(json["params"])) 270 | } 271 | 272 | private func textDocument(codeLens json: JSValue) throws -> LanguageServerCommand { 273 | return .textDocumentCodeLens( 274 | requestId: try RequestId.decode(json["id"]), 275 | params: try CodeLensParams.decode(json["params"])) 276 | } 277 | 278 | private func codeLens(resolve json: JSValue) throws -> LanguageServerCommand { 279 | return .codeLensResolve( 280 | requestId: try RequestId.decode(json["id"]), 281 | params: try CodeLens.decode(json["params"])) 282 | } 283 | 284 | private func textDocument(documentLink json: JSValue) throws -> LanguageServerCommand { 285 | return .textDocumentDocumentLink( 286 | requestId: try RequestId.decode(json["id"]), 287 | params: try DocumentLinkParams.decode(json["params"])) 288 | } 289 | 290 | private func documentLink(resolve json: JSValue) throws -> LanguageServerCommand { 291 | return .documentLinkResolve( 292 | requestId: try RequestId.decode(json["id"]), 293 | params: try DocumentLink.decode(json["params"])) 294 | } 295 | 296 | private func textDocument(rename json: JSValue) throws -> LanguageServerCommand { 297 | return .textDocumentRename( 298 | requestId: try RequestId.decode(json["id"]), 299 | params: try RenameParams.decode(json["params"])) 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Sources/JsonRpcProtocol/Serialization.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | import LanguageServerProtocol 8 | 9 | 10 | 11 | // NOTE(owensd): These two protocols should maybe move to the JSONLib library itself. 12 | 13 | extension JSValue { 14 | var integer: Int? { 15 | if let number = self.number { 16 | return Int(number) 17 | } 18 | return nil 19 | } 20 | } 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Cancellation.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | /// The base protocol offers support for request cancellation. To cancel a request, a notification 7 | /// message with the following properties is sent: 8 | /// Notification: 9 | /// method: `$/cancelRequest` 10 | /// params: `CancelParams` defined as follows: 11 | public struct CancelParams { 12 | public init(id: RequestId) { 13 | self.id = id 14 | } 15 | 16 | /// The ID of the request to cancel. 17 | public var id: RequestId 18 | } 19 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Capabilities.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | /// Client capabilities got introduced with the version 3.0 of the protocol. They therefore only 9 | /// describe capabilities that got introduced in 3.x or later. Capabilities that existed in the 2.x 10 | /// version of the protocol are still mandatory for clients. Clients cannot opt out of providing 11 | /// them. So even if a client omits the `ClientCapabilities.textDocument.synchronization` it is 12 | /// still required that the client provides text document synchronization (e.g. open, changed and 13 | /// close notifications). 14 | public struct ClientCapabilities { 15 | public init( 16 | workspace: WorkspaceClientCapabilities? = nil, 17 | textDocument: TextDocumentClientCapabilities? = nil, 18 | experimental: AnyEncodable? = nil) { 19 | self.workspace = workspace 20 | self.textDocument = textDocument 21 | self.experimental = experimental 22 | } 23 | 24 | /// Workspace specific client capabilities. 25 | public var workspace: WorkspaceClientCapabilities? 26 | 27 | /// Text document specific client capabilities. 28 | public var textDocument: TextDocumentClientCapabilities? 29 | 30 | /// Experimental client capabilities. 31 | public var experimental: AnyEncodable? 32 | } 33 | 34 | /// `TextDocumentClientCapabilities` define capabilities the editor/tool provides on text documents. 35 | public struct TextDocumentClientCapabilities { 36 | public init( 37 | synchronization: SynchronizationCapability? = nil, 38 | completion: CompletionCapability? = nil, 39 | hover: DynamicRegistrationCapability? = nil, 40 | signatureHelp: DynamicRegistrationCapability? = nil, 41 | references: DynamicRegistrationCapability? = nil, 42 | documentHighlight: DynamicRegistrationCapability? = nil, 43 | documentSymbol: DynamicRegistrationCapability? = nil, 44 | formatting: DynamicRegistrationCapability? = nil, 45 | rangeFormatting: DynamicRegistrationCapability? = nil, 46 | onTypeFormatting: DynamicRegistrationCapability? = nil, 47 | definition: DynamicRegistrationCapability? = nil, 48 | codeAction: DynamicRegistrationCapability? = nil, 49 | codeLens: DynamicRegistrationCapability? = nil, 50 | documentLink: DynamicRegistrationCapability? = nil, 51 | rename: DynamicRegistrationCapability? = nil) { 52 | self.synchronization = synchronization 53 | self.completion = completion 54 | self.hover = hover 55 | self.signatureHelp = signatureHelp 56 | self.references = references 57 | self.documentHighlight = documentHighlight 58 | self.documentSymbol = documentSymbol 59 | self.formatting = formatting 60 | self.rangeFormatting = rangeFormatting 61 | self.onTypeFormatting = onTypeFormatting 62 | self.definition = definition 63 | self.codeAction = codeAction 64 | self.codeLens = codeLens 65 | self.documentLink = documentLink 66 | self.rename = rename 67 | } 68 | 69 | public var synchronization: SynchronizationCapability? 70 | 71 | /// Capabilities specific to the `textDocument/completion` 72 | public var completion: CompletionCapability? 73 | /// Capabilities specific to the `textDocument/hover` 74 | public var hover: DynamicRegistrationCapability? 75 | 76 | /// Capabilities specific to the `textDocument/signatureHelp` 77 | public var signatureHelp: DynamicRegistrationCapability? 78 | 79 | /// Capabilities specific to the `textDocument/references` 80 | public var references: DynamicRegistrationCapability? 81 | 82 | /// Capabilities specific to the `textDocument/documentHighlight` 83 | public var documentHighlight: DynamicRegistrationCapability? 84 | 85 | /// Capabilities specific to the `textDocument/documentSymbol` 86 | public var documentSymbol: DynamicRegistrationCapability? 87 | 88 | /// Capabilities specific to the `textDocument/formatting` 89 | public var formatting: DynamicRegistrationCapability? 90 | 91 | /// Capabilities specific to the `textDocument/rangeFormatting` 92 | public var rangeFormatting: DynamicRegistrationCapability? 93 | 94 | /// Capabilities specific to the `textDocument/onTypeFormatting` 95 | public var onTypeFormatting: DynamicRegistrationCapability? 96 | 97 | /// Capabilities specific to the `textDocument/definition` 98 | public var definition: DynamicRegistrationCapability? 99 | 100 | /// Capabilities specific to the `textDocument/codeAction` 101 | public var codeAction: DynamicRegistrationCapability? 102 | 103 | /// Capabilities specific to the `textDocument/codeLens` 104 | public var codeLens: DynamicRegistrationCapability? 105 | 106 | /// Capabilities specific to the `textDocument/documentLink` 107 | public var documentLink: DynamicRegistrationCapability? 108 | 109 | /// Capabilities specific to the `textDocument/rename` 110 | public var rename: DynamicRegistrationCapability? 111 | } 112 | 113 | public struct SynchronizationCapability { 114 | public init( 115 | dynamicRegistration: Bool? = nil, 116 | willSave: Bool? = nil, 117 | willSaveWaitUntil: Bool? = nil, 118 | didSave: Bool? = nil) { 119 | self.dynamicRegistration = dynamicRegistration 120 | self.willSave = willSave 121 | self.willSaveWaitUntil = willSaveWaitUntil 122 | self.didSave = didSave 123 | } 124 | 125 | /// Whether text document synchronization supports dynamic registration. 126 | public var dynamicRegistration: Bool? 127 | 128 | /// The client supports sending will save notifications. 129 | public var willSave: Bool? 130 | 131 | /// The client supports sending a will save request and waits for a response providing text 132 | /// edits which will be applied to the document before it is saved. 133 | public var willSaveWaitUntil: Bool? 134 | 135 | /// The client supports did save notifications. 136 | public var didSave: Bool? 137 | } 138 | 139 | public struct DynamicRegistrationCapability { 140 | public init(dynamicRegistration: Bool? = nil) { 141 | self.dynamicRegistration = dynamicRegistration 142 | } 143 | 144 | public var dynamicRegistration: Bool? 145 | } 146 | 147 | public struct CompletionCapability { 148 | public init(dynamicRegistration: Bool? = nil, completionItem: CompletionItemCapability? = nil) { 149 | self.dynamicRegistration = dynamicRegistration 150 | self.completionItem = completionItem 151 | } 152 | 153 | public var dynamicRegistration: Bool? 154 | public var completionItem: CompletionItemCapability? 155 | } 156 | 157 | /// Client supports snippets as insert text. 158 | /// 159 | /// A snippet can define tab stops and placeholders with `$1`, `$2` and `${3:foo}`. `$0` defines the 160 | /// final tab stop, it defaults to the end of the snippet. Placeholders with equal identifiers are 161 | /// linked, that is typing in one will update others too. 162 | public struct CompletionItemCapability { 163 | public init(snippetSupport: Bool? = nil) { 164 | self.snippetSupport = snippetSupport 165 | } 166 | 167 | public var snippetSupport: Bool? 168 | } 169 | 170 | /// `WorkspaceClientCapabilities` define capabilities the editor/tool provides on the workspace: 171 | public struct WorkspaceClientCapabilities { 172 | public init( 173 | applyEdit: Bool? = nil, 174 | workspaceEdit: DocumentChangesCapability? = nil, 175 | didChangeConfiguration: DynamicRegistrationCapability? = nil, 176 | didChangeWatchedFiles: DynamicRegistrationCapability? = nil, 177 | symbol: DynamicRegistrationCapability? = nil, 178 | executeCommand: DynamicRegistrationCapability? = nil) { 179 | self.applyEdit = applyEdit 180 | self.workspaceEdit = workspaceEdit 181 | self.didChangeConfiguration = didChangeConfiguration 182 | self.didChangeWatchedFiles = didChangeWatchedFiles 183 | self.symbol = symbol 184 | self.executeCommand = executeCommand 185 | } 186 | 187 | /// The client supports applying batch edits to the workspace by supporting the request 188 | /// 'workspace/applyEdit' 189 | public var applyEdit: Bool? 190 | 191 | /// Capabilities specific to `WorkspaceEdit`s 192 | public var workspaceEdit: DocumentChangesCapability? 193 | 194 | /// Capabilities specific to the `workspace/didChangeConfiguration` notification. 195 | public var didChangeConfiguration: DynamicRegistrationCapability? 196 | 197 | /// Capabilities specific to the `workspace/didChangeWatchedFiles` notification. 198 | public var didChangeWatchedFiles: DynamicRegistrationCapability? 199 | 200 | /// Capabilities specific to the `workspace/symbol` request. 201 | public var symbol: DynamicRegistrationCapability? 202 | 203 | /// Capabilities specific to the `workspace/executeCommand` request. 204 | public var executeCommand: DynamicRegistrationCapability? 205 | } 206 | 207 | public struct DocumentChangesCapability { 208 | public init(documentChanges: Bool? = nil) { 209 | self.documentChanges = documentChanges 210 | } 211 | 212 | /// The client supports versioned document changes in `WorkspaceEdit`s 213 | public var documentChanges: Bool? 214 | } 215 | 216 | public enum TextDocumentSync { 217 | case options(TextDocumentSyncOptions) 218 | case kind(TextDocumentSyncKind) 219 | } 220 | 221 | public struct ServerCapabilities { 222 | public init( 223 | textDocumentSync: TextDocumentSync? = nil, 224 | hoverProvider: Bool? = nil, 225 | completionProvider: CompletionOptions? = nil, 226 | signatureHelpProvider: SignatureHelpOptions? = nil, 227 | definitionProvider: Bool? = nil, 228 | referencesProvider: Bool? = nil, 229 | documentHighlightProvider: Bool? = nil, 230 | documentSymbolProvider: Bool? = nil, 231 | workspaceSymbolProvider: Bool? = nil, 232 | codeActionProvider: Bool? = nil, 233 | codeLensProvider: CodeLensOptions? = nil, 234 | documentFormattingProvider: Bool? = nil, 235 | documentRangeFormattingProvider: Bool? = nil, 236 | documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions? = nil, 237 | renameProvider: Bool? = nil, 238 | documentLinkProvider: DocumentLinkOptions? = nil, 239 | executeCommandProvider: ExecuteCommandOptions? = nil, 240 | experimental: Any? = nil) { 241 | self.textDocumentSync = textDocumentSync 242 | self.hoverProvider = hoverProvider 243 | self.completionProvider = completionProvider 244 | self.signatureHelpProvider = signatureHelpProvider 245 | self.definitionProvider = definitionProvider 246 | self.referencesProvider = referencesProvider 247 | self.documentHighlightProvider = documentHighlightProvider 248 | self.documentSymbolProvider = documentSymbolProvider 249 | self.workspaceSymbolProvider = workspaceSymbolProvider 250 | self.codeActionProvider = codeActionProvider 251 | self.codeLensProvider = codeLensProvider 252 | self.documentFormattingProvider = documentFormattingProvider 253 | self.documentRangeFormattingProvider = documentRangeFormattingProvider 254 | self.documentOnTypeFormattingProvider = documentOnTypeFormattingProvider 255 | self.renameProvider = renameProvider 256 | self.documentLinkProvider = documentLinkProvider 257 | self.executeCommandProvider = executeCommandProvider 258 | self.experimental = experimental 259 | } 260 | 261 | /// Defines how text documents are synced. Is either a detailed structure defining each 262 | /// notification or for backwards compatibility the `TextDocumentSyncKind` number. 263 | public var textDocumentSync: TextDocumentSync? = nil 264 | 265 | /// The server provides hover support. 266 | public var hoverProvider: Bool? = nil 267 | 268 | /// The server provides completion support. 269 | public var completionProvider: CompletionOptions? = nil 270 | 271 | /// The server provides signature help support. 272 | public var signatureHelpProvider: SignatureHelpOptions? = nil 273 | 274 | /// The server provides goto definition support. 275 | public var definitionProvider: Bool? = nil 276 | 277 | /// The server provides find references support. 278 | public var referencesProvider: Bool? = nil 279 | 280 | /// The server provides document highlight support. 281 | public var documentHighlightProvider: Bool? = nil 282 | 283 | /// The server provides document symbol support. 284 | public var documentSymbolProvider: Bool? = nil 285 | 286 | /// The server provides workspace symbol support. 287 | public var workspaceSymbolProvider: Bool? = nil 288 | 289 | /// The server provides code actions. 290 | public var codeActionProvider: Bool? = nil 291 | 292 | /// The server provides code lens. 293 | public var codeLensProvider: CodeLensOptions? = nil 294 | 295 | /// The server provides document formatting. 296 | public var documentFormattingProvider: Bool? = nil 297 | 298 | /// The server provides document range formatting. 299 | public var documentRangeFormattingProvider: Bool? = nil 300 | 301 | /// The server provides document formatting on typing. 302 | public var documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions? = nil 303 | 304 | /// The server provides rename support. 305 | public var renameProvider: Bool? = nil 306 | 307 | /// The server provides document link support. 308 | public var documentLinkProvider: DocumentLinkOptions? = nil 309 | 310 | /// The server provides execute command support. 311 | public var executeCommandProvider: ExecuteCommandOptions? = nil 312 | 313 | /// Experimental server capabilities. 314 | public var experimental: Any? = nil 315 | } 316 | 317 | public struct TextDocumentSyncOptions { 318 | public init( 319 | openClose: Bool? = nil, 320 | change: TextDocumentSyncKind? = nil, 321 | willSave: Bool? = nil, 322 | willSaveWaitUntil: Bool? = nil, 323 | save: SaveOptions? = nil) { 324 | self.openClose = openClose 325 | self.change = change 326 | self.willSave = willSave 327 | self.willSaveWaitUntil = willSaveWaitUntil 328 | self.save = save 329 | } 330 | 331 | /// Open and close notifications are sent to the server. 332 | public var openClose: Bool? 333 | 334 | /// Change notifications are sent to the server. 335 | public var change: TextDocumentSyncKind? 336 | 337 | /// Will save notifications are sent to the server. 338 | public var willSave: Bool? 339 | 340 | /// Will save wait until requests are sent to the server. 341 | public var willSaveWaitUntil: Bool? 342 | 343 | /// Save notifications are sent to the server. 344 | public var save: SaveOptions? 345 | } 346 | 347 | /// Save options. 348 | public struct SaveOptions { 349 | public init(includeText: Bool? = nil) { 350 | self.includeText = includeText 351 | } 352 | 353 | /// The client is supposed to include the content on save. 354 | public var includeText: Bool? 355 | } 356 | 357 | /// Defines how the host (editor) should sync document changes to the language server. 358 | public enum TextDocumentSyncKind: Int { 359 | /// Documents should not be synced at all. 360 | case none = 0 361 | 362 | /// Documents are synced by always sending the full content of the document. 363 | case full = 1 364 | 365 | /// Documents are synced by sending the full content on open. After that only incremental 366 | /// updates to the document are send. 367 | case incremental = 2 368 | } 369 | 370 | /// The options to dictate how completion triggers should work. 371 | public struct CompletionOptions { 372 | public init(resolveProvider: Bool? = nil, triggerCharacters: [String]? = nil) { 373 | self.resolveProvider = resolveProvider 374 | self.triggerCharacters = triggerCharacters 375 | } 376 | 377 | /// The server provides support to resolve additional information for a completion item. 378 | public var resolveProvider: Bool? 379 | 380 | /// The characters that trigger completion automatically. 381 | public var triggerCharacters: [String]? 382 | } 383 | 384 | public struct CodeLensOptions { 385 | public init(resolveProvider: Bool? = nil) { 386 | self.resolveProvider = resolveProvider 387 | } 388 | 389 | public var resolveProvider: Bool? 390 | } 391 | 392 | public struct SignatureHelpOptions { 393 | public init(triggerCharacters: [String]? = nil) { 394 | self.triggerCharacters = triggerCharacters 395 | } 396 | 397 | public var triggerCharacters: [String]? 398 | } 399 | 400 | public struct DocumentOnTypeFormattingOptions { 401 | public init(trigger: String, moreTriggerCharacter: [String]? = nil) { 402 | self.firstTriggerCharacter = trigger 403 | self.moreTriggerCharacter = moreTriggerCharacter 404 | } 405 | 406 | public var firstTriggerCharacter: String 407 | public var moreTriggerCharacter: [String]? 408 | } 409 | 410 | public struct DocumentLinkOptions { 411 | public init(resolveProvider: Bool? = nil) { 412 | self.resolveProvider = resolveProvider 413 | } 414 | 415 | public var resolveProvider: Bool? 416 | } 417 | 418 | public struct ExecuteCommandOptions { 419 | public init(commands: [String]? = nil) { 420 | self.commands = commands 421 | } 422 | 423 | public var commands: [String]? 424 | } 425 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/ClientRegistration.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | /// General parameters to to register for a capability. 9 | public struct Registration { 10 | public init(id: String, method: String, registerOptions: AnyEncodable? = nil) { 11 | self.id = id 12 | self.method = method 13 | self.registerOptions = registerOptions 14 | } 15 | 16 | /// The id used to register the request. The id can be used to deregister the request again. 17 | public var id: String 18 | 19 | /// The method / capability to register for. 20 | public var method: String 21 | 22 | /// Options necessary for the registration. 23 | public var registerOptions: AnyEncodable? 24 | } 25 | 26 | public struct RegistrationParams { 27 | public init(registrations: [Registration]) { 28 | self.registrations = registrations 29 | } 30 | 31 | public var registrations: [Registration]; 32 | } 33 | 34 | /// General parameters to unregister a capability. 35 | public struct Unregistration { 36 | public init(id: String, method: String) { 37 | self.id = id 38 | self.method = method 39 | } 40 | 41 | /// The id used to unregister the request or notification. Usually an id provided during the 42 | /// register request. 43 | public var id: String 44 | 45 | /// The method / capability to unregister for. 46 | public var method: String 47 | } 48 | 49 | public struct UnregistrationParams { 50 | public init(unregisterations: [Unregistration]) { 51 | self.unregisterations = unregisterations 52 | } 53 | 54 | public var unregisterations: [Unregistration]; 55 | } 56 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Command.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | /// Represents a reference to a command. Provides a title which will be used to represent a command 9 | /// in the UI. Commands are identified using a string identifier and the protocol currently doesn't 10 | /// specify a set of well known commands. So executing a command requires some tool extension code. 11 | public struct Command { 12 | public init(title: String, command: String, arguments: AnyEncodable? = nil) { 13 | self.title = title 14 | self.command = command 15 | self.arguments = arguments 16 | } 17 | 18 | /// Title of the command, like `save`. 19 | public var title: String 20 | 21 | /// The identifier of the actual command handler. 22 | public var command: String 23 | 24 | /// Arguments that the command handler should be invoked with. 25 | public var arguments: AnyEncodable? 26 | } 27 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Completion.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | /// Represents a collection of [completion items](#CompletionItem) to be presented 9 | /// in the editor. 10 | public struct CompletionList { 11 | public init(isIncomplete: Bool, items: [CompletionItem]) { 12 | self.isIncomplete = isIncomplete 13 | self.items = items 14 | } 15 | 16 | /// This list it not complete. Further typing should result in recomputing 17 | /// this list. 18 | public var isIncomplete: Bool 19 | 20 | /// The completion items. 21 | public var items: [CompletionItem] 22 | } 23 | 24 | /// Defines whether the insert text in a completion item should be interpreted as 25 | /// plain text or a snippet. 26 | public enum InsertTextFormat: Int { 27 | /// The primary text to be inserted is treated as a plain string. 28 | case PlainText = 1 29 | 30 | /// The primary text to be inserted is treated as a snippet. 31 | /// 32 | /// A snippet can define tab stops and placeholders with `$1`, `$2` and `${3:foo}`. `$0` defines 33 | /// the final tab stop, it defaults to the end of the snippet. Placeholders with equal 34 | /// identifiers are linked, that is typing in one will update others too. 35 | /// 36 | /// See also: 37 | /// https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md 38 | case Snippet = 2 39 | } 40 | 41 | public struct CompletionItem { 42 | public init( 43 | label: String, 44 | kind: CompletionItemKind? = nil, 45 | detail: String? = nil, 46 | documentation: String? = nil, 47 | sortText: String? = nil, 48 | filterText: String? = nil, 49 | insertText: String? = nil, 50 | insertTextFormat: InsertTextFormat? = nil, 51 | textEdit: TextEdit? = nil, 52 | additionalTextEdits: [TextEdit]? = nil, 53 | command: Command? = nil, 54 | data: AnyEncodable? = nil) { 55 | self.label = label 56 | self.kind = kind 57 | self.detail = detail 58 | self.documentation = documentation 59 | self.sortText = sortText 60 | self.filterText = filterText 61 | self.insertText = insertText 62 | self.insertTextFormat = insertTextFormat 63 | self.textEdit = textEdit 64 | self.additionalTextEdits = additionalTextEdits 65 | self.command = command 66 | self.data = data 67 | } 68 | 69 | /// The label of this completion item. By default also the text that is inserted when selecting 70 | /// this completion. 71 | public var label: String 72 | 73 | /// The kind of this completion item. Based of the kind an icon is chosen by the editor. 74 | public var kind: CompletionItemKind? 75 | 76 | /// A human-readable string with additional information 77 | /// about this item, like type or symbol information. 78 | public var detail: String? 79 | 80 | /// A human-readable string that represents a doc-comment. 81 | public var documentation: String? 82 | 83 | /// A string that should be used when comparing this item with other items. When `falsy` the 84 | /// label is used. 85 | public var sortText: String? 86 | 87 | /// A string that should be used when filtering a set of completion items. When `falsy` the 88 | /// label is used. 89 | public var filterText: String? 90 | 91 | /// A string that should be inserted a document when selecting this completion. When `falsy` the 92 | /// label is used. 93 | public var insertText: String? 94 | 95 | /// The format of the insert text. The format applies to both the `insertText` property and the 96 | /// `newText` property of a provided `textEdit`. 97 | public var insertTextFormat: InsertTextFormat? 98 | 99 | /// An edit which is applied to a document when selecting this completion. When an edit is 100 | /// provided the value of `insertText` is ignored. 101 | /// 102 | /// Note: The range of the edit must be a single line range and it must contain the position at 103 | /// which completion has been requested. 104 | public var textEdit: TextEdit? 105 | 106 | /// An optional array of additional text edits that are applied when selecting this completion. 107 | /// Edits must not overlap with the main edit nor with themselves. 108 | public var additionalTextEdits: [TextEdit]? 109 | 110 | /// An optional command that is executed *after* inserting this completion. 111 | /// 112 | /// Note: that additional modifications to the current document should be described with the 113 | /// additionalTextEdits-property. 114 | public var command: Command? 115 | 116 | /// An data entry field that is preserved on a completion item between a completion and a 117 | /// completion resolve request. 118 | public var data: AnyEncodable? 119 | } 120 | 121 | /// The kind of a completion entry. 122 | public enum CompletionItemKind: Int { 123 | case text = 1 124 | case method = 2 125 | case function = 3 126 | case constructor = 4 127 | case field = 5 128 | case variable = 6 129 | case `class` = 7 130 | case interface = 8 131 | case module = 9 132 | case property = 10 133 | case unit = 11 134 | case value = 12 135 | case `enum` = 13 136 | case keyword = 14 137 | case snippet = 15 138 | case color = 16 139 | case file = 17 140 | case reference = 18 141 | } 142 | 143 | public struct ReferenceParams { 144 | public init(textDocument: TextDocumentIdentifier, position: Position, context: ReferenceContext) { 145 | self.textDocument = textDocument 146 | self.position = position 147 | self.context = context 148 | } 149 | 150 | /// The text document. 151 | public var textDocument: TextDocumentIdentifier 152 | 153 | /// The position inside the text document. 154 | public var position: Position 155 | 156 | public var context: ReferenceContext 157 | } 158 | 159 | public struct ReferenceContext { 160 | public init(includeDeclaration: Bool) { 161 | self.includeDeclaration = includeDeclaration 162 | } 163 | 164 | /// Include the declaration of the current symbol. 165 | public var includeDeclaration: Bool 166 | } 167 | 168 | public enum CompletionListResult { 169 | case completionItems([CompletionItem]) 170 | case completionList(CompletionList) 171 | } 172 | 173 | /// Represents information about programming constructs like variables, classes, interfaces etc. 174 | public struct SymbolInformation { 175 | 176 | public init(name: String, kind: SymbolKind, location: Location, containerName: String? = nil) { 177 | self.name = name 178 | self.kind = kind 179 | self.location = location 180 | self.containerName = containerName 181 | } 182 | 183 | /// The name of this symbol. 184 | public var name: String 185 | 186 | /// The kind of this symbol. 187 | public var kind: SymbolKind 188 | 189 | /// The location of this symbol. 190 | public var location: Location 191 | 192 | /// The name of the symbol containing this symbol. 193 | public var containerName: String? 194 | } 195 | 196 | /// A symbol kind. 197 | public enum SymbolKind: Int { 198 | case file = 1 199 | case module = 2 200 | case namespace = 3 201 | case package = 4 202 | case `class` = 5 203 | case method = 6 204 | case property = 7 205 | case field = 8 206 | case constructor = 9 207 | case `enum` = 10 208 | case interface = 11 209 | case function = 12 210 | case variable = 13 211 | case constant = 14 212 | case string = 15 213 | case number = 16 214 | case boolean = 17 215 | case array = 18 216 | } 217 | 218 | public struct Hover { 219 | 220 | public init(contents: [MarkedString], range: Range? = nil) { 221 | self.contents = contents 222 | self.range = range 223 | } 224 | 225 | public var contents: [MarkedString] 226 | 227 | /// An optional range is a range inside a text document that is used to visualize a hover, e.g. 228 | /// by changing the background color. 229 | public var range: Range? 230 | } 231 | 232 | /// MarkedString can be used to render human readable text. It is either a markdown string or a 233 | /// code-block that provides a language and a code snippet. The language identifier is semantically 234 | /// equal to the optional language identifier in fenced code blocks in GitHub issues. See 235 | /// https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting 236 | /// 237 | /// The pair of a language and a value is an equivalent to markdown: 238 | /// ```${language} 239 | /// ${value} 240 | /// ``` 241 | /// 242 | /// Note that markdown strings will be sanitized - that means html will be escaped. 243 | public enum MarkedString { 244 | case string(String) 245 | case code(language: String, value: String) 246 | } 247 | 248 | /// Signature help represents the signature of something callable. There can be multiple signature 249 | /// but only one active and only one active parameter. 250 | public struct SignatureHelp { 251 | 252 | public init( 253 | signatures: [SignatureInformation], 254 | activeSignature: Int? = nil, 255 | activeParameter: Int? = nil) { 256 | self.signatures = signatures 257 | self.activeSignature = activeSignature 258 | self.activeParameter = activeParameter 259 | } 260 | 261 | /// One or more signatures. 262 | public var signatures: [SignatureInformation] 263 | 264 | /// The active signature. If omitted or the value lies outside the range of `signatures` the 265 | /// value defaults to zero or is ignored if `signatures.length === 0`. Whenever possible 266 | /// implementors should make an active decision about the active signature and shouldn't rely on 267 | /// a default value. In future version of the protocol this property might become mandatory to 268 | /// better express this. 269 | public var activeSignature: Int? 270 | 271 | /// The active parameter of the active signature. If omitted or the value lies outside the range 272 | /// of `signatures[activeSignature].parameters` defaults to 0 if the active signature has 273 | /// parameters. If the active signature has no parameters it is ignored. In future version of 274 | /// the protocol this property might become mandatory to better express the active parameter if 275 | /// the active signature does have any. 276 | public var activeParameter: Int? 277 | } 278 | 279 | /// Represents the signature of something callable. A signature can have a label, like a 280 | /// function-name, a doc-comment, and a set of parameters. 281 | public struct SignatureInformation { 282 | 283 | public init(label: String, documentation: String? = nil, parameters: [ParameterInformation]? = nil) { 284 | self.label = label 285 | self.documentation = documentation 286 | self.parameters = parameters 287 | } 288 | 289 | /// The label of this signature. Will be shown in the UI. 290 | public var label: String 291 | 292 | /// The human-readable doc-comment of this signature. Will be shown in the UI but can be 293 | /// omitted. 294 | public var documentation: String? 295 | 296 | /// The parameters of this signature. 297 | public var parameters: [ParameterInformation]? 298 | } 299 | 300 | /// Represents a parameter of a callable-signature. A parameter can have a label and a doc-comment. 301 | public struct ParameterInformation { 302 | 303 | public init(label: String, documentation: String? = nil) { 304 | self.label = label 305 | self.documentation = documentation 306 | } 307 | 308 | /// The label of this parameter. Will be shown in the UI. 309 | public var label: String 310 | 311 | /// The human-readable doc-comment of this parameter. Will be shown in the UI but can be 312 | /// omitted. 313 | public var documentation: String? 314 | } 315 | 316 | /// A document highlight is a range inside a text document which deserves 317 | /// special attention. Usually a document highlight is visualized by changing 318 | /// the background color of its range. 319 | public struct DocumentHighlight { 320 | 321 | public init(range: Range, kind: DocumentHighlightKind? = nil) { 322 | self.range = range 323 | self.kind = kind 324 | } 325 | 326 | /// The range this highlight applies to. 327 | public var range: Range 328 | 329 | /// The highlight kind, default is DocumentHighlightKind.Text. 330 | public var kind: DocumentHighlightKind? 331 | } 332 | 333 | /// A document highlight kind. 334 | public enum DocumentHighlightKind: Int { 335 | /// A textual occurrence. 336 | case text = 1 337 | 338 | /// Read-access of a symbol, like reading a variable. 339 | case read = 2 340 | 341 | /// Write-access of a symbol, like writing to a variable. 342 | case write = 3 343 | } -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Diagnostics.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | /// Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only 7 | /// valid in the scope of a resource. 8 | public struct Diagnostic { 9 | public init(range: Range, message: String, severity: DiagnosticSeverity? = nil, code: DiagnosticCode? = nil, source: String? = nil) { 10 | self.range = range 11 | self.message = message 12 | self.severity = severity 13 | self.code = code 14 | self.source = source 15 | } 16 | 17 | /// The range at which the message applies. 18 | public var range: Range 19 | 20 | /// The diagnostics' severity. Can be omitted. If omitted it is up to the client to interpret 21 | /// diagnostics as error, warning, info or hint. 22 | public var severity: DiagnosticSeverity? 23 | 24 | /// The diagnostics code. Can be omitted. 25 | public var code: DiagnosticCode? 26 | 27 | /// A human-readable string describing the source of this diagnostic, e.g. 'typescript' or 28 | /// 'super lint'. 29 | public var source: String? 30 | 31 | /// The diagnostics message. 32 | public var message: String 33 | } 34 | 35 | /// A code to use within the `Diagnostic` type. 36 | public enum DiagnosticCode { 37 | case number(Int) 38 | case string(String) 39 | } 40 | 41 | /// The protocol currently supports the following diagnostic severities: 42 | public enum DiagnosticSeverity: Int { 43 | /// Reports an error. 44 | case error = 1 45 | /// Reports a warning. 46 | case warning = 2 47 | /// Reports an information. 48 | case information = 3 49 | /// Reports a hint. 50 | case hint = 4 51 | } 52 | 53 | public enum TraceSetting { 54 | case off 55 | case messages 56 | case verbose 57 | } 58 | 59 | public struct PublishDiagnosticsParams { 60 | public init(uri: DocumentUri, diagnostics: [Diagnostic]) { 61 | self.uri = uri 62 | self.diagnostics = diagnostics 63 | } 64 | 65 | /// The URI for which diagnostic information is reported. 66 | public var uri: DocumentUri 67 | 68 | /// An array of diagnostic information items. 69 | public var diagnostics: [Diagnostic] 70 | } -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/ErrorCodes.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | /// A set of pre-defined error codes that can be used when an error occurs. 7 | public enum /*namespace*/ ErrorCodes { 8 | // Defined by JSON RPC 9 | static let parseError = -32700 10 | static let invalidRequest = -32600 11 | static let methodNotFound = -32601 12 | static let invalidParams = -32602 13 | static let internalError = -32603 14 | static let serverErrorStart = -32099 15 | static let serverErrorEnd = -32000 16 | static let serverNotInitialized = -32002 17 | static let unknownErrorCode = -32001 18 | 19 | // Defined by the protocol. 20 | static let requestCancelled = -32800 21 | } -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Formatting.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | public struct DocumentFormattingParams { 7 | public init(textDocument: TextDocumentIdentifier, options: FormattingOptions) { 8 | self.textDocument = textDocument 9 | self.options = options 10 | } 11 | 12 | /// The document to format. 13 | public var textDocument: TextDocumentIdentifier 14 | 15 | /// The format options. 16 | public var options: FormattingOptions 17 | } 18 | 19 | /// Value-object describing what options formatting should use. 20 | public struct FormattingOptions { 21 | public init(tabSize: Int = 4, insertSpaces: Bool = true) { 22 | self.tabSize = tabSize 23 | self.insertSpaces = insertSpaces 24 | } 25 | 26 | /// Size of a tab in spaces. 27 | public var tabSize: Int 28 | 29 | /// Prefer spaces over tabs. 30 | public var insertSpaces: Bool 31 | 32 | // Signature for further properties. 33 | //[key: string]: boolean | number | string; 34 | } 35 | 36 | public struct DocumentRangeFormattingParams { 37 | public init(textDocument: TextDocumentIdentifier, range: Range, options: FormattingOptions) { 38 | self.textDocument = textDocument 39 | self.range = range 40 | self.options = options 41 | } 42 | 43 | /// The document to format. 44 | public var textDocument: TextDocumentIdentifier 45 | 46 | /// The range to format 47 | public var range: Range 48 | 49 | /// The format options 50 | public var options: FormattingOptions 51 | } 52 | 53 | public struct DocumentOnTypeFormattingParams { 54 | public init(textDocument: TextDocumentIdentifier, position: Position, ch: String, options: FormattingOptions) { 55 | self.textDocument = textDocument 56 | self.position = position 57 | self.ch = ch 58 | self.options = options 59 | } 60 | 61 | /// The document to format. 62 | public var textDocument: TextDocumentIdentifier 63 | 64 | /// The position at which this request was sent. 65 | public var position: Position 66 | 67 | /// The character that has been typed. 68 | public var ch: String 69 | 70 | /// The format options. 71 | public var options: FormattingOptions 72 | } -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Initialization.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | /// The set of parameters that are used for the `initialize` method. 7 | public struct InitializeParams { 8 | public init(processId: Int? = nil, rootPath: String? = nil, rootUri: DocumentUri? = nil, initializationOptions: Any? = nil, capabilities: ClientCapabilities = ClientCapabilities(), trace: TraceSetting? = nil) { 9 | self.processId = processId 10 | self.rootPath = rootPath 11 | self.rootUri = rootUri 12 | self.initializationOptions = initializationOptions 13 | self.capabilities = capabilities 14 | self.trace = trace 15 | } 16 | 17 | /// The process Id of the parent process that started the server. Is `null` if the process 18 | /// has not been started by another process. 19 | /// If the parent process is not alive then the server should exit (see exit notification) 20 | /// its process. 21 | public var processId: Int? = nil 22 | 23 | /// The rootPath of the workspace. Is `null` if no folder is open. 24 | @available(*, deprecated:3.0, message: "The `rootUri` member should be used instead.") 25 | public var rootPath: String? = nil 26 | 27 | /// The root URI of the workspace. Is `null`` if no folder is open. If both `rootPath` and 28 | /// `rootUri` are set, `rootUri` wins. 29 | public var rootUri: DocumentUri? = nil 30 | 31 | /// User provided initialization options. 32 | public var initializationOptions: Any? = nil 33 | 34 | /// The capabilities provided by the client (editor or tool). 35 | public var capabilities = ClientCapabilities() 36 | 37 | /// The initial trace setting. If omitted trace is disabled ('off'). 38 | public var trace: TraceSetting? = nil 39 | } 40 | 41 | /// The response to an `Initialize` request. 42 | public struct InitializeResult { 43 | public init(capabilities: ServerCapabilities) { 44 | self.capabilities = capabilities 45 | } 46 | 47 | /// This should return all of the capabilities that the server supports. 48 | public var capabilities: ServerCapabilities 49 | } 50 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/InputOutput/StandardInputOutputBuffer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | #if os(Linux) 7 | import Glibc 8 | #else 9 | import Darwin 10 | import os.log 11 | #endif 12 | 13 | import Foundation 14 | import Dispatch 15 | 16 | @available(macOS 10.12, *) 17 | fileprivate let log = OSLog(subsystem: "com.kiadstudios.languageserverprotocol", category: "StandardInputOutputBuffer") 18 | 19 | /// A message source that pulls data in from `stdin`. 20 | public final class StandardInputOutputBuffer: InputOutputBuffer { 21 | /// Internal queue used to handle the message input source. 22 | private var inputQueue = DispatchQueue( 23 | label: "com.kiadstudios.swiftlangsrv.standardinputbuffer", 24 | attributes: .concurrent 25 | ) 26 | 27 | /// Whew! Look at the value of this! 28 | public init() {} 29 | 30 | /// Continuously monitors the `stdin`. 31 | public func run(received: @escaping (Message) -> Message?) { 32 | inputQueue.async { 33 | let messageBuffer = MessageBuffer() 34 | var input = fd_set() 35 | 36 | while (true) { 37 | zero(fd: &input) 38 | set(descriptor: STDIN_FILENO, fd: &input) 39 | 40 | let maxMessageSize = 8192 41 | var buffer = [UInt8](repeating: 0, count: maxMessageSize) 42 | 43 | let result = select(STDIN_FILENO + 1, &input, nil, nil, nil) 44 | if (result == -1) { 45 | fatalError("*** error in select() ***") 46 | } 47 | else if (result > 0) { 48 | let bytesRead = read(STDIN_FILENO, &buffer, maxMessageSize) 49 | if bytesRead < 0 { 50 | if #available(macOS 10.12, *) { 51 | os_log("unable to read from buffer: %d", log: log, type: .default, errno) 52 | } 53 | fatalError("Unable to read from the input buffer.") 54 | } 55 | else if bytesRead > 0 { 56 | let messages = messageBuffer.write(data: [UInt8](buffer[0.. EncodableType 22 | } 23 | 24 | /// Defines a protocol that is useful when the type is not part of the known types for the Language 25 | /// Server Protocol. This is necessary due to limitations in Swift's type system representation. 26 | /// Without this, we have to expose leaky abstractions from our serialization layers, such as 27 | /// `JSValue`. 28 | public protocol AnyEncodable { 29 | func encode() -> Any 30 | } 31 | 32 | /// Defines a generic protocol used to decode a given type into the data necessary for use within 33 | /// the `MessageProtocol` layers. 34 | public protocol Decodable { 35 | associatedtype EncodableType 36 | static func decode(_ data: EncodableType?) throws -> Self 37 | } 38 | 39 | /// Defines a type that is both `Encodable` and `Decodable`. 40 | public typealias Codeable = Encodable & Decodable 41 | 42 | 43 | /// Defines the body of the message contract defined in the `Language Server Protocol`. This 44 | /// provides one additional layer of provide a `data` agnostic approach to passing the data around. 45 | /// While the spec call for JSON-RPC only, this `Message` allows authors to provide a different 46 | /// implementation of a transport mechanism vai the `MessageProtocol` protocol for efficiency 47 | /// purposes. 48 | public struct Message { 49 | /// The details for the message header. 50 | public let header: MessageHeader 51 | 52 | /// The set of data that is coming as part of the message body. The size of this data is defined 53 | /// as the the `Content-Length` field within the message header. The encoding of this data 54 | /// should be defined with the `Content-Type` field within the message header; this defaults to 55 | /// UTF8. 56 | public let content: MessageData 57 | 58 | public init(header: MessageHeader, content: MessageData) { 59 | self.header = header 60 | self.content = content 61 | 62 | } 63 | } 64 | 65 | /// The header for all messages. 66 | public struct MessageHeader { 67 | /// The key for the `Content-Length` property. This is one of the supported header fields. 68 | public static let contentLengthKey = "Content-Length" 69 | 70 | /// The key for the `Content-Type` property. This is one of the supported header fields. 71 | public static let contentTypeKey = "Content-Type" 72 | 73 | /// All message headers must be terminated with the sequence of `\r\n\r\n` as per spec. 74 | /// NOTE: There is discussion about allowing other terminators like `\n\n` as well. This parser 75 | /// implementation will allow for both `\n\n` and `\r\r` as a convenience. However, when 76 | /// serializing the header out, the standard `\r\n\r\n` will be used. 77 | public static let messageHeaderSeparator = "\r\n\r\n" 78 | 79 | /// The default value for the `Content-Type` field if one is not explicitly given. 80 | public static let defaultContentType = "application/vscode-jsonrpc; charset=utf-8" 81 | 82 | public init(headerFields: [String:String] = [:]) { 83 | self.headerFields = headerFields 84 | } 85 | 86 | /// The set of header fields. This parser will parse all header field values, not just those 87 | /// defined in the spec. Note that all values will be held as a string, any type coercion must 88 | /// be handled by the message consumer. 89 | public var headerFields: [String:String] 90 | 91 | /// Retrieves the value of `Content-Length` from `headerFields`. 92 | public var contentLength: Int { 93 | if let length = headerFields[MessageHeader.contentLengthKey] { 94 | return Int(length) ?? 0 95 | } 96 | 97 | return 0 98 | } 99 | 100 | /// Retrieves the value of the `Content-Type` from `headerFields` or the default value. 101 | public var contentType: String { 102 | return headerFields[MessageHeader.contentTypeKey] ?? MessageHeader.defaultContentType 103 | } 104 | } 105 | 106 | extension MessageHeader: CustomStringConvertible { 107 | /// Provides the printout as required by the LSP spec. 108 | public var description: String { 109 | var content = "" 110 | for (key, value) in self.headerFields { 111 | content += "\(key): \(value)\r\n" 112 | } 113 | return content + "\r\n" 114 | } 115 | } 116 | 117 | /// The bottom layer in the messaging stack that is the source of the raw message data. 118 | public protocol InputOutputBuffer { 119 | /// Starts listening for new messages to come in. Whenever a message comes in, the `received` 120 | /// closure is invoked. It is also the responsibility of this function to write out the response 121 | /// message. 122 | /// Implementation note: this function is intended to spawn a new thread for handling incoming 123 | /// message data. As such, this is a non-blocking function call. 124 | func run(received: @escaping (Message) -> Message?) 125 | 126 | /// Used to signal that the message source should stop processing input feeds. 127 | func stop() 128 | } 129 | 130 | /// A message protocol is a layer that is used to convert an incoming message of type `MessageData` 131 | /// into a usable `LanguageServerCommand`. If that message cannot be converted, then the `translate` 132 | /// function will throw. 133 | public protocol MessageProtocol { 134 | /// The internal type representation of the message content for the protocol. 135 | associatedtype ProtocolMessageType 136 | 137 | /// The form for all message parsers. If the raw message data cannot be converted into a 138 | /// `LanguageServerCommand`, the parser should throw with a detailed error message. 139 | typealias MessageParser = (ProtocolMessageType) throws -> LanguageServerCommand 140 | 141 | /// The registration table for all of the commands that can be handled via this protocol. 142 | var protocols: [String:MessageParser] { get } 143 | 144 | /// Translates the data from the raw `MessageData` to a valid `ProtocolDataType`. This function 145 | /// can throw, providing detailed error information about why the transformation could not be 146 | /// done. 147 | func translate(message: Message) throws -> LanguageServerCommand 148 | 149 | /// Translates the response into a `Message`. This function can throw, providing detailed error 150 | /// information about why the transformation could not be done. 151 | func translate(response: LanguageServerResponse) throws -> Message 152 | } 153 | 154 | 155 | /// Defines the API to describe a command for the language server. 156 | public enum LanguageServerCommand { 157 | case initialize(requestId: RequestId, params: InitializeParams) 158 | case initialized 159 | case shutdown(requestId: RequestId) 160 | case exit 161 | case cancelRequest(params: CancelParams) 162 | 163 | case windowShowMessageRequest(requestId: RequestId, params: ShowMessageRequestParams) 164 | 165 | case clientRegisterCapability(requestId: RequestId, params: RegistrationParams) 166 | case clientUnregisterCapability(requestId: RequestId, params: UnregistrationParams) 167 | 168 | case workspaceDidChangeConfiguration(params: DidChangeConfigurationParams) 169 | case workspaceDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) 170 | case workspaceSymbol(requestId: RequestId, params: WorkspaceSymbolParams) 171 | case workspaceExecuteCommand(requestId: RequestId, params: ExecuteCommandParams) 172 | case workspaceApplyEdit(requestId: RequestId, params: ApplyWorkspaceEditParams) 173 | 174 | case textDocumentDidOpen(params: DidOpenTextDocumentParams) 175 | case textDocumentDidChange(params: DidChangeTextDocumentParams) 176 | case textDocumentWillSave(params: WillSaveTextDocumentParams) 177 | case textDocumentWillSaveWaitUntil(requestId: RequestId, params: WillSaveTextDocumentParams) 178 | case textDocumentDidSave(params: DidSaveTextDocumentParams) 179 | case textDocumentDidClose(params: DidCloseTextDocumentParams) 180 | case textDocumentCompletion(requestId: RequestId, params: TextDocumentPositionParams) 181 | case completionItemResolve(requestId: RequestId, params: CompletionItem) 182 | case textDocumentHover(requestId: RequestId, params: TextDocumentPositionParams) 183 | case textDocumentSignatureHelp(requestId: RequestId, params: TextDocumentPositionParams) 184 | case textDocumentReferences(requestId: RequestId, params: ReferenceParams) 185 | case textDocumentDocumentHighlight(requestId: RequestId, params: TextDocumentPositionParams) 186 | case textDocumentDocumentSymbol(requestId: RequestId, params: DocumentSymbolParams) 187 | case textDocumentFormatting(requestId: RequestId, params: DocumentFormattingParams) 188 | case textDocumentRangeFormatting(requestId: RequestId, params: DocumentRangeFormattingParams) 189 | case textDocumentOnTypeFormatting(requestId: RequestId, params: DocumentOnTypeFormattingParams) 190 | case textDocumentDefinition(requestId: RequestId, params: TextDocumentPositionParams) 191 | case textDocumentCodeAction(requestId: RequestId, params: CodeActionParams) 192 | case textDocumentCodeLens(requestId: RequestId, params: CodeLensParams) 193 | case codeLensResolve(requestId: RequestId, params: CodeLens) 194 | case textDocumentDocumentLink(requestId: RequestId, params: DocumentLinkParams) 195 | case documentLinkResolve(requestId: RequestId, params: DocumentLink) 196 | case textDocumentRename(requestId: RequestId, params: RenameParams) 197 | } 198 | 199 | public enum LanguageServerResponse { 200 | case initialize(requestId: RequestId, result: InitializeResult) 201 | case shutdown(requestId: RequestId) 202 | 203 | case showMessageRequest(requestId: RequestId, result: ShowMessageRequestParams) 204 | 205 | case clientRegisterCapability(requestId: RequestId) 206 | case clientUnregisterCapability(requestId: RequestId) 207 | 208 | case workspaceSymbol(requestId: RequestId, result: [SymbolInformation]) 209 | case workspaceExecuteCommand(requestId: RequestId) 210 | 211 | case completionItemResolve(requestId: RequestId, result: Hover) 212 | case codeLensResolve(requestId: RequestId, result: CodeLens) 213 | case documentLinkResolve(requestId: RequestId, result: DocumentLink) 214 | 215 | case textDocumentWillSaveWaitUntil(requestId: RequestId, result: [TextEdit]) 216 | case textDocumentCompletion(requestId: RequestId, result: CompletionListResult) 217 | case textDocumentHover(requestId: RequestId, result: Hover) 218 | case textDocumentSignatureHelp(requestId: RequestId, result: SignatureHelp) 219 | case textDocumentReferences(requestId: RequestId, result: [Location]) 220 | case textDocumentDocumentHighlight(requestId: RequestId, result: [DocumentHighlight]) 221 | case textDocumentDocumentSymbol(requestId: RequestId, result: [SymbolInformation]) 222 | case textDocumentFormatting(requestId: RequestId, result: [TextEdit]) 223 | case textDocumentRangeFormatting(requestId: RequestId, result: [TextEdit]) 224 | case textDocumentOnTypeFormatting(requestId: RequestId, result: [TextEdit]) 225 | case textDocumentDefinition(requestId: RequestId, result: [Location]) 226 | case textDocumentCodeAction(requestId: RequestId, result: [Command]) 227 | case textDocumentCodeLens(requestId: RequestId, result: [CodeLens]) 228 | case textDocumentDocumentLink(requestId: RequestId, result: [DocumentLink]?) 229 | case textDocumentRename(requestId: RequestId, result: WorkspaceEdit) 230 | 231 | // The set of "responses" that are simply initiated from the server; no request from the client 232 | // is necessary. 233 | case windowShowMessage(params: ShowMessageParams) 234 | case windowLogMessage(params: LogMessageParams) 235 | case telemetryEvent(params: AnyEncodable) 236 | case textDocumentPublishDiagnostics(params: PublishDiagnosticsParams) 237 | } 238 | 239 | 240 | /// Helper to convert the message into a printable output. 241 | extension Message: CustomStringConvertible { 242 | public var description: String { 243 | var output = "" 244 | for (key, value) in self.header.headerFields { 245 | output += "\(key): \(value)\r\n" 246 | } 247 | 248 | let content = String(bytes: self.content, encoding: .utf8) ?? "" 249 | output += "\r\n\(content)" 250 | 251 | return output 252 | } 253 | } 254 | 255 | /// A request ID used to coordinate request/response pairs. 256 | public enum RequestId { 257 | /// The numeric value for the request ID. 258 | case number(Int) 259 | /// The string value for the request ID. 260 | case string(String) 261 | } 262 | 263 | /// This is a utility class that provides a queueing mechanism for incoming messages. This is 264 | /// exposed publicly to allow other authors of an `InputBuffer` to share this logic as this should 265 | /// be shared across all implementations. This class takes a given set of raw bytes that comes in 266 | /// and parses those into the header and content parts. So long as the messages data follows the 267 | /// spec for VS Code encoded messages, this will work regardless of the internal format that is 268 | /// chosen for the data within the message. 269 | public final class MessageBuffer { 270 | /// The local cache of bytes that has been read so far. 271 | private var buffer: [UInt8] = [] 272 | 273 | /// Exposing publicly... 274 | public init() {} 275 | 276 | /// Appends the data to the current set of data. If possible, any messages that can be parsed out 277 | /// of all of the received data will be returned. Once a message is returned, its content is 278 | /// removed from the internal buffer. 279 | public func write(data: MessageData) -> [Message] { 280 | buffer += data 281 | 282 | var messages: [Message] = [] 283 | 284 | do { 285 | while (true) { 286 | if let (header, headerSize) = try parse(header: buffer) { 287 | if let (content, contentSize) = try parse(header: header, offset: headerSize, body: buffer) { 288 | messages.append(Message(header: header, content: content)) 289 | buffer = [UInt8](buffer.dropFirst(contentSize + headerSize)) 290 | continue 291 | } 292 | } 293 | 294 | break 295 | } 296 | } 297 | catch { 298 | fatalError("content is incorrect...") 299 | } 300 | 301 | return messages 302 | } 303 | 304 | private func parse(header buffer: [UInt8]) throws -> (MessageHeader, Int)? { 305 | enum ParserState { 306 | case field 307 | case value 308 | } 309 | 310 | func isNewline(_ c: Character) -> Bool { 311 | return c == "\n" || c == "\r" || c == "\r\n" 312 | } 313 | 314 | func isValidHeaderFieldCharacter(_ c: Character) -> Bool { 315 | return (UnicodeScalar("\(c)")?.isASCII ?? false) && c != ":" && !isNewline(c) 316 | } 317 | 318 | func isValidHeaderValueCharacter(_ c: Character) -> Bool { 319 | return (UnicodeScalar("\(c)")?.isASCII ?? false) && !isNewline(c) 320 | } 321 | 322 | var header = MessageHeader() 323 | var field = "" 324 | var value = "" 325 | 326 | var state = ParserState.field 327 | var headerSize = 0 328 | var skip = false 329 | 330 | for (index, byte) in buffer.enumerated() { 331 | headerSize += 1 332 | if skip { skip = false; continue } 333 | 334 | let c = Character(UnicodeScalar(byte)) 335 | 336 | switch state { 337 | case .field: 338 | if isValidHeaderFieldCharacter(c) { 339 | field += "\(c)" 340 | } 341 | else if c == ":" { 342 | state = .value 343 | value = "" 344 | } 345 | else if c == "\r" { 346 | if let next = buffer.at(index + 1) { 347 | let nextC = Character(UnicodeScalar(next)) 348 | if nextC == "\n" { 349 | headerSize += 1 350 | } 351 | } 352 | return (header, headerSize) 353 | } 354 | else if c == "\n" { 355 | return (header, headerSize) 356 | } 357 | else { 358 | throw "The value '\(c)' is not expected or allowed here." 359 | } 360 | 361 | case .value: 362 | if isValidHeaderValueCharacter(c) { 363 | value += "\(c)" 364 | } 365 | else if c == "\r" || c == "\n" { 366 | if c == "\r" { 367 | if let next = buffer.at(index + 1) { 368 | let nextC = Character(UnicodeScalar(next)) 369 | if nextC == "\n" { 370 | skip = true 371 | } 372 | } 373 | } 374 | 375 | value = value.trimmingCharacters(in: .whitespaces) 376 | if value != "" { 377 | header.headerFields[field] = value 378 | state = .field 379 | field = "" 380 | } 381 | else { 382 | throw "An empty value is not supported." 383 | } 384 | } 385 | } 386 | } 387 | 388 | return nil 389 | } 390 | 391 | private func parse(header: MessageHeader, offset: Int, body buffer: [UInt8]) throws -> (MessageData, Int)? { 392 | let length = header.contentLength 393 | if length <= 0 { throw "Invalid content length: \(length)" } 394 | let messageSize = offset + length 395 | if buffer.count < messageSize { return nil } 396 | 397 | return ([UInt8](buffer[offset.. Element? { 23 | return (index < self.count) ? self[index] : nil 24 | } 25 | } -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/TextDocument.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | /// TODO(owensd): Properly implement this according to the spec. 9 | public typealias DocumentUri = String 10 | 11 | /// Position in a text document expressed as zero-based line and character offset. A position is 12 | /// between two characters like an 'insert' cursor in a editor. 13 | public struct Position { 14 | public init(line: Int, character: Int) { 15 | self.line = line 16 | self.character = character 17 | } 18 | 19 | /// Line position in a document (zero-based). 20 | public var line: Int 21 | 22 | /// Character offset on a line in a document (zero-based). 23 | public var character: Int 24 | } 25 | 26 | /// A range in a text document expressed as (zero-based) start and end positions. A range is 27 | /// comparable to a selection in an editor. Therefore the end position is exclusive. 28 | public struct Range { 29 | public init(start: Position, end: Position) { 30 | self.start = start 31 | self.end = end 32 | } 33 | 34 | /// The range's start position. 35 | public var start: Position 36 | 37 | /// The range's end position. 38 | public var end: Position 39 | } 40 | 41 | /// Represents a location inside a resource, such as a line inside a text file. 42 | public struct Location { 43 | public init(uri: DocumentUri, range: Range) { 44 | self.uri = uri 45 | self.range = range 46 | } 47 | 48 | /// The URI of the document the location belongs to. 49 | public var uri: DocumentUri 50 | 51 | /// The full range that should make up the range. 52 | public var range: Range 53 | } 54 | 55 | /// A textual edit applicable to a text document. 56 | /// 57 | /// If multiple TextEdits are applied to a text document, all text edits describe changes made to 58 | /// the initial document version. Execution wise text edits should applied from the bottom to the 59 | /// top of the text document. Overlapping text edits are not supported. 60 | public struct TextEdit { 61 | public init(range: Range, newText: String) { 62 | self.range = range 63 | self.newText = newText 64 | } 65 | 66 | /// The range of the text document to be manipulated. To insert text into a document create 67 | /// a range where `start === end`. 68 | public var range: Range 69 | 70 | /// The string to be inserted. For delete operations use an empty string. 71 | public var newText: String 72 | } 73 | 74 | /// Describes textual changes on a single text document. The text document is referred to as a 75 | /// `VersionedTextDocumentIdentifier` to allow clients to check the text document version before an 76 | /// edit is applied. 77 | public struct TextDocumentEdit { 78 | public init(textDocument: VersionedTextDocumentIdentifier, edits: [TextEdit]) { 79 | self.textDocument = textDocument 80 | self.edits = edits 81 | } 82 | 83 | /// The text document to change. 84 | public var textDocument: VersionedTextDocumentIdentifier 85 | 86 | /// The edits to be applied. 87 | public var edits: [TextEdit] 88 | } 89 | 90 | /// A workspace edit represents changes to many resources managed in the workspace. The edit should 91 | /// either provide `changes` or `documentChanges`. If `documentChanges` are present they are 92 | /// preferred over changes if the client can handle versioned document edits. 93 | public enum WorkspaceEdit { 94 | /// Holds changes to existing resources. 95 | case changes([DocumentUri:[TextEdit]]) 96 | 97 | /// An array of `TextDocumentEdit`s to express changes to specific a specific version of a text 98 | /// document. Whether a client supports versioned document edits is expressed via 99 | /// `WorkspaceClientCapabilities.versionedWorkspaceEdit`. 100 | case documentChanges([TextDocumentEdit]) 101 | } 102 | 103 | /// Text documents are identified using a URI. On the protocol level, URIs are passed as strings. 104 | /// The corresponding JSON structure looks like this: 105 | public struct TextDocumentIdentifier { 106 | /// The text document's URI. 107 | public var uri: DocumentUri 108 | 109 | public init(uri: DocumentUri) { 110 | self.uri = uri 111 | } 112 | } 113 | 114 | /// An item to transfer a text document from the client to the server. 115 | public struct TextDocumentItem { 116 | public init(uri: DocumentUri, languageId: String, version: Int, text: String) { 117 | self.uri = uri 118 | self.languageId = languageId 119 | self.version = version 120 | self.text = text 121 | } 122 | 123 | /// The text document's URI. 124 | public var uri: DocumentUri 125 | 126 | /// The text document's language identifier. 127 | public var languageId: String 128 | 129 | /// The version number of this document (it will strictly increase after each change, including 130 | /// undo/redo). 131 | public var version: Int 132 | 133 | /// The content of the opened text document. 134 | public var text: String 135 | } 136 | 137 | /// An identifier to denote a specific version of a text document. 138 | public struct VersionedTextDocumentIdentifier /* : TextDocumentIdentifier */ { 139 | /// The text document's URI. 140 | public var uri: DocumentUri 141 | 142 | /// The version number of this document. 143 | public var version: Int 144 | 145 | public init(uri: DocumentUri, version: Int) { 146 | self.uri = uri 147 | self.version = version 148 | } 149 | } 150 | 151 | /// A parameter literal used in requests to pass a text document and a position inside that 152 | /// document. 153 | public struct TextDocumentPositionParams { 154 | public init(textDocument: TextDocumentIdentifier, position: Position) { 155 | self.textDocument = textDocument 156 | self.position = position 157 | } 158 | 159 | /// The text document. 160 | public var textDocument: TextDocumentIdentifier 161 | 162 | /// The position inside the text document. 163 | public var position: Position 164 | } 165 | 166 | /// A document filter denotes a document through properties like language, schema or pattern. 167 | /// Examples are a filter that applies to TypeScript files on disk or a filter the applies to JSON 168 | /// files with name package.json: 169 | /// 170 | /// { language: 'typescript', scheme: 'file' } 171 | /// { language: 'json', pattern: '**/package.json' } 172 | public struct DocumentFilter { 173 | public init(language: String? = nil, scheme: String? = nil, pattern: String? = nil) { 174 | self.language = language 175 | self.scheme = scheme 176 | self.pattern = pattern 177 | } 178 | 179 | /// A language id, like `typescript`. 180 | public var language: String? 181 | 182 | /// A Uri [scheme](#Uri.scheme), like `file` or `untitled`. 183 | public var scheme: String? 184 | 185 | /// A glob pattern, like `*.{ts,js}`. 186 | public var pattern: String? 187 | } 188 | 189 | public struct DidOpenTextDocumentParams { 190 | public init(textDocument: TextDocumentItem) { 191 | self.textDocument = textDocument 192 | } 193 | 194 | /// The document that was opened. 195 | public var textDocument: TextDocumentItem 196 | } 197 | 198 | public struct DidChangeTextDocumentParams { 199 | public init(textDocument: VersionedTextDocumentIdentifier, contentChanges: [TextDocumentContentChangeEvent]) { 200 | self.textDocument = textDocument 201 | self.contentChanges = contentChanges 202 | } 203 | 204 | /// The document that did change. The version number points to the version after all provided content changes have been applied. 205 | public var textDocument: VersionedTextDocumentIdentifier 206 | 207 | /// The actual content changes. 208 | public var contentChanges: [TextDocumentContentChangeEvent] 209 | } 210 | 211 | /// An event describing a change to a text document. If range and rangeLength are omitted the new 212 | /// text is considered to be the full content of the document. 213 | public struct TextDocumentContentChangeEvent { 214 | public init(text: String, range: Range? = nil, rangeLength: Int? = nil) { 215 | self.text = text 216 | self.range = range 217 | self.rangeLength = rangeLength 218 | } 219 | 220 | /// The range of the document that changed. 221 | public var range: Range? 222 | 223 | /// The length of the range that got replaced. 224 | public var rangeLength: Int? 225 | 226 | /// The new text of the range/document. 227 | public var text: String 228 | } 229 | 230 | /// The parameters send in a will save text document notification. 231 | public struct WillSaveTextDocumentParams { 232 | public init(textDocument: TextDocumentIdentifier, reason: TextDocumentSaveReason) { 233 | self.textDocument = textDocument 234 | self.reason = reason 235 | } 236 | 237 | /// The document that will be saved. 238 | public var textDocument: TextDocumentIdentifier 239 | 240 | /// The 'TextDocumentSaveReason'. 241 | public var reason: TextDocumentSaveReason 242 | } 243 | 244 | /// Represents reasons why a text document is saved. 245 | public enum TextDocumentSaveReason: Int { 246 | /// Manually triggered, e.g. by the user pressing save, by starting debugging, or by an API 247 | /// call. 248 | case manual = 1 249 | 250 | /// Automatic after a delay. 251 | case afterDelay = 2 252 | 253 | /// When the editor lost focus. 254 | case focusOut = 3 255 | } 256 | 257 | public struct DidSaveTextDocumentParams { 258 | public init(textDocument: TextDocumentIdentifier, text: String? = nil) { 259 | self.textDocument = textDocument 260 | self.text = text 261 | } 262 | 263 | /// The document that was saved. 264 | public var textDocument: TextDocumentIdentifier 265 | 266 | /// Optional the content when saved. Depends on the includeText value when the save notification 267 | /// was requested. 268 | public var text: String? 269 | } 270 | 271 | public struct DidCloseTextDocumentParams { 272 | public init(textDocument: TextDocumentIdentifier) { 273 | self.textDocument = textDocument 274 | } 275 | 276 | /// The document that was closed. 277 | public var textDocument: TextDocumentIdentifier 278 | } 279 | 280 | public struct DocumentSymbolParams { 281 | public init(textDocument: TextDocumentIdentifier) { 282 | self.textDocument = textDocument 283 | } 284 | 285 | /// The text document. 286 | public var textDocument: TextDocumentIdentifier 287 | } 288 | 289 | public struct CodeActionParams { 290 | public init(textDocument: TextDocumentIdentifier, range: Range, context: CodeActionContext) { 291 | self.textDocument = textDocument 292 | self.range = range 293 | self.context = context 294 | } 295 | 296 | /// The document in which the command was invoked. 297 | public var textDocument: TextDocumentIdentifier 298 | 299 | /// The range for which the command was invoked. 300 | public var range: Range 301 | 302 | /// Context carrying additional information. 303 | public var context: CodeActionContext 304 | } 305 | 306 | /// Contains additional diagnostic information about the context in which a code action is run. 307 | public struct CodeActionContext { 308 | public init(diagnostics: [Diagnostic]) { 309 | self.diagnostics = diagnostics 310 | } 311 | 312 | /// An array of diagnostics. 313 | public var diagnostics: [Diagnostic] 314 | } 315 | 316 | public struct CodeLensParams { 317 | public init(textDocument: TextDocumentIdentifier) { 318 | self.textDocument = textDocument 319 | } 320 | 321 | /// The document to request code lens for. 322 | public var textDocument: TextDocumentIdentifier 323 | } 324 | 325 | /// A code lens represents a command that should be shown along with source text, like the number of 326 | /// references, a way to run tests, etc. 327 | /// 328 | /// A code lens is _unresolved_ when no command is associated to it. For performance reasons the 329 | /// creation of a code lens and resolving should be done in two stages. 330 | public struct CodeLens { 331 | public init(range: Range, command: Command? = nil, data: AnyEncodable? = nil) { 332 | self.range = range 333 | self.command = command 334 | self.data = data 335 | } 336 | 337 | /// The range in which this code lens is valid. Should only span a single line. 338 | public var range: Range 339 | 340 | /// The command this code lens represents. 341 | public var command: Command? 342 | 343 | /// A data entry field that is preserved on a code lens item between 344 | /// a code lens and a code lens resolve request. 345 | public var data: AnyEncodable? 346 | } 347 | 348 | public struct DocumentLinkParams { 349 | public init(textDocument: TextDocumentIdentifier) { 350 | self.textDocument = textDocument 351 | } 352 | 353 | /// The document to provide document links for. 354 | public var textDocument: TextDocumentIdentifier 355 | } 356 | 357 | /// A document link is a range in a text document that links to an internal or external resource, 358 | /// like another text document or a web site. 359 | public struct DocumentLink { 360 | public init(range: Range, target: DocumentUri? = nil) { 361 | self.range = range 362 | self.target = target 363 | } 364 | 365 | /// The range this link applies to. 366 | public var range: Range 367 | 368 | /// The uri this link points to. If missing a resolve request is sent later. 369 | public var target: DocumentUri? 370 | } 371 | 372 | public struct RenameParams { 373 | public init(textDocument: TextDocumentIdentifier, position: Position, newName: String) { 374 | self.textDocument = textDocument 375 | self.position = position 376 | self.newName = newName 377 | } 378 | 379 | /// The document to format. 380 | public var textDocument: TextDocumentIdentifier 381 | 382 | /// The position at which this request was sent. 383 | public var position: Position 384 | 385 | /// The new name of the symbol. If the given name is not valid the request must return a 386 | /// [ResponseError](#ResponseError) with an appropriate message set. 387 | public var newName: String 388 | } 389 | 390 | /// A document selector is the combination of one or many document filters. 391 | public typealias DocumentSelector = [DocumentFilter]; 392 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Workspace.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import JSONLib 7 | 8 | public struct DidChangeConfigurationParams { 9 | public init(settings: AnyEncodable) { 10 | self.settings = settings 11 | } 12 | 13 | /// The actual changed settings 14 | public var settings: AnyEncodable 15 | } 16 | 17 | public struct DidChangeWatchedFilesParams { 18 | public init(changes: [FileEvent]) { 19 | self.changes = changes 20 | } 21 | 22 | /// The actual file events. 23 | public var changes: [FileEvent] 24 | } 25 | 26 | /// An event describing a file change. 27 | public struct FileEvent { 28 | public init(uri: DocumentUri, type: FileChangeType) { 29 | self.uri = uri 30 | self.type = type 31 | } 32 | 33 | /// The file's URI. 34 | public var uri: DocumentUri 35 | 36 | /// The change type. 37 | public var type: FileChangeType 38 | } 39 | 40 | /// The file event type. 41 | public enum FileChangeType: Int { 42 | case created = 1 43 | case changed = 2 44 | case deleted = 3 45 | } 46 | 47 | /// The parameters of a Workspace Symbol Request. 48 | public struct WorkspaceSymbolParams { 49 | public init(query: String) { 50 | self.query = query 51 | } 52 | 53 | /// A non-empty query string 54 | public var query: String 55 | } 56 | 57 | public struct ExecuteCommandParams { 58 | public init(command: String, arguments: [String]? = nil) { 59 | self.command = command 60 | self.arguments = arguments 61 | } 62 | 63 | /// The identifier of the actual command handler. 64 | public var command: String 65 | 66 | /// Arguments that the command should be invoked with. 67 | public var arguments: [String]? 68 | } 69 | 70 | public struct ApplyWorkspaceEditParams { 71 | public init(edit: WorkspaceEdit) { 72 | self.edit = edit 73 | } 74 | 75 | /// The edits to apply. 76 | public var edit: WorkspaceEdit 77 | } -------------------------------------------------------------------------------- /Tests/JsonRpcProtocolTests/EncodableTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import XCTest 7 | @testable import LanguageServerProtocol 8 | @testable import JsonRpcProtocol 9 | import JSONLib 10 | 11 | final class EncodableTests: XCTestCase { 12 | 13 | func testEncodable001() { 14 | let encoded = VersionedTextDocumentIdentifier(uri: "./foo/a.swift", version: 1).encode() 15 | XCTAssertEqual(encoded["uri"].string, "./foo/a.swift") 16 | XCTAssertEqual(encoded["version"].integer, 1) 17 | } 18 | 19 | func testEncodable002() { 20 | let encoded = CompletionOptions(resolveProvider: true, triggerCharacters: [".", ";"]).encode() 21 | XCTAssertEqual(encoded["resolveProvider"].bool, true) 22 | XCTAssertEqual(encoded["triggerCharacters"].array?.first?.string, ".") 23 | XCTAssertEqual(encoded["triggerCharacters"].array?.last?.string, ";") 24 | } 25 | 26 | func testEncodable003() { 27 | let encoded = TextDocumentSyncKind.full.encode() 28 | XCTAssertEqual(encoded.number, 1) 29 | } 30 | 31 | func testEncodable004() { 32 | let diagnostic = Diagnostic( 33 | range: LanguageServerProtocol.Range( 34 | start: Position(line: 1, character: 0), 35 | end: Position(line: 1, character: 10) 36 | ), 37 | message: "happy days", 38 | severity: .warning, 39 | code: nil, 40 | source: nil 41 | ) 42 | 43 | let encoded = diagnostic.encode() 44 | XCTAssertEqual(encoded["range"]["start"]["line"].integer, 1) 45 | XCTAssertEqual(encoded["range"]["start"]["character"].integer, 0) 46 | XCTAssertEqual(encoded["range"]["end"]["line"].integer, 1) 47 | XCTAssertEqual(encoded["range"]["end"]["character"].integer, 10) 48 | XCTAssertEqual(encoded["severity"].integer, 2) 49 | XCTAssertTrue(encoded["code"] == nil) 50 | XCTAssertTrue(encoded["source"] == nil) 51 | XCTAssertEqual(encoded["message"].string, "happy days") 52 | } 53 | 54 | func testEncodable005() { 55 | let options: JSValue = [ 56 | "hello": "world", 57 | "just": [ 58 | "some": "data", 59 | "value": 12 60 | ] 61 | ] 62 | let registration = Registration(id: "1234", method: "complete", registerOptions: options) 63 | let encoded = registration.encode() 64 | XCTAssertEqual(encoded["id"].string, "1234") 65 | XCTAssertEqual(encoded["method"].string, "complete") 66 | XCTAssertEqual(encoded["registerOptions"]["hello"].string, "world") 67 | XCTAssertEqual(encoded["registerOptions"]["just"]["some"].string, "data") 68 | XCTAssertEqual(encoded["registerOptions"]["just"]["value"].integer, 12) 69 | } 70 | 71 | 72 | static var allTests = [ 73 | ("testEncodable001", testEncodable001), 74 | ("testEncodable002", testEncodable002), 75 | ("testEncodable003", testEncodable003), 76 | ("testEncodable004", testEncodable004), 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/MessageBufferTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Kiad Studios, LLC. All rights reserved. 3 | * Licensed under the MIT License. See License in the project root for license information. 4 | */ 5 | 6 | import XCTest 7 | @testable import LanguageServerProtocol 8 | 9 | final class MessageBufferTests: XCTestCase { 10 | func testMessageBuffer001() { 11 | let buffer = MessageBuffer() 12 | let header = "Content-Length: 133\r\n\r\n" 13 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 14 | let message = "\(header)\(content)" 15 | 16 | let messages = buffer.write(data: [UInt8](message.data(using: .utf8)!)) 17 | XCTAssertEqual(messages.count, 1) 18 | 19 | if let message = messages.first { 20 | XCTAssertEqual(message.header.contentLength, 133) 21 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 22 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 23 | } 24 | else { 25 | XCTFail() 26 | } 27 | } 28 | 29 | func testMessageBuffer002() { 30 | let buffer = MessageBuffer() 31 | let header = "Content-Length: 133\n\n" 32 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 33 | let message = "\(header)\(content)" 34 | 35 | let messages = buffer.write(data: [UInt8](message.data(using: .utf8)!)) 36 | XCTAssertEqual(messages.count, 1) 37 | 38 | if let message = messages.first { 39 | XCTAssertEqual(message.header.contentLength, 133) 40 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 41 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 42 | } 43 | else { 44 | XCTFail() 45 | } 46 | } 47 | 48 | func testMessageBuffer003() { 49 | let buffer = MessageBuffer() 50 | let header = "Content-Length: 133\r\nContent-Type: application/vscode-jsonrpc; charset=utf-16\r\n\r\n" 51 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 52 | let message = "\(header)\(content)" 53 | 54 | let messages = buffer.write(data: [UInt8](message.data(using: .utf8)!)) 55 | XCTAssertEqual(messages.count, 1) 56 | 57 | if let message = messages.first { 58 | XCTAssertEqual(message.header.contentLength, 133) 59 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-16") 60 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 61 | } 62 | else { 63 | XCTFail() 64 | } 65 | } 66 | 67 | func testMessageBuffer004() { 68 | let buffer = MessageBuffer() 69 | let header = "Content-Length: 133\r\n\r\n" 70 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 71 | let message = "\(header)\(content)\(header)\(content)" 72 | 73 | var messages = buffer.write(data: [UInt8](message.data(using: .utf8)!)) 74 | XCTAssertEqual(messages.count, 2) 75 | 76 | if let message = messages.first { 77 | XCTAssertEqual(message.header.contentLength, 133) 78 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 79 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 80 | } 81 | else { 82 | XCTFail() 83 | } 84 | 85 | messages = [Message](messages.dropFirst(1)) 86 | XCTAssertEqual(messages.count, 1) 87 | 88 | if let message = messages.first { 89 | XCTAssertEqual(message.header.contentLength, 133) 90 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 91 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 92 | } 93 | else { 94 | XCTFail() 95 | } 96 | } 97 | 98 | func testMessageBuffer005() { 99 | let buffer = MessageBuffer() 100 | let header = "Content-Length: 133\r\n\r\n" 101 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 102 | 103 | var messages = buffer.write(data: [UInt8](header.data(using: .utf8)!)) 104 | XCTAssertEqual(messages.count, 0) 105 | 106 | messages = buffer.write(data: [UInt8](content.data(using: .utf8)!)) 107 | 108 | if let message = messages.first { 109 | XCTAssertEqual(message.header.contentLength, 133) 110 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 111 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 112 | } 113 | else { 114 | XCTFail() 115 | } 116 | } 117 | 118 | func testMessageBuffer006() { 119 | let buffer = MessageBuffer() 120 | let header1 = "Content-Length" 121 | let header2 = ": 133\r\n\r\n" 122 | let content = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 123 | 124 | var messages = buffer.write(data: [UInt8](header1.data(using: .utf8)!)) 125 | XCTAssertEqual(messages.count, 0) 126 | 127 | messages = buffer.write(data: [UInt8](header2.data(using: .utf8)!)) 128 | XCTAssertEqual(messages.count, 0) 129 | 130 | messages = buffer.write(data: [UInt8](content.data(using: .utf8)!)) 131 | XCTAssertEqual(messages.count, 1) 132 | 133 | if let message = messages.first { 134 | XCTAssertEqual(message.header.contentLength, 133) 135 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 136 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, content) 137 | } 138 | else { 139 | XCTFail() 140 | } 141 | } 142 | 143 | func testMessageBuffer007() { 144 | let buffer = MessageBuffer() 145 | let header = "Content-Length: 133\r\n\r\n" 146 | let content1 = "{\"jsonrpc\": \"2.0\", \"id\": 0, \"method\": \"initialize\", \"params\"" 147 | let content2 = ": { \"processId\": 0, \"rootPath\": \"/Users/owensd/foo\", \"capabilities\": {}}}" 148 | 149 | var messages = buffer.write(data: [UInt8](header.data(using: .utf8)!)) 150 | XCTAssertEqual(messages.count, 0) 151 | 152 | messages = buffer.write(data: [UInt8](content1.data(using: .utf8)!)) 153 | XCTAssertEqual(messages.count, 0) 154 | messages = buffer.write(data: [UInt8](content2.data(using: .utf8)!)) 155 | XCTAssertEqual(messages.count, 1) 156 | 157 | if let message = messages.first { 158 | XCTAssertEqual(message.header.contentLength, 133) 159 | XCTAssertEqual(message.header.contentType, "application/vscode-jsonrpc; charset=utf-8") 160 | XCTAssertEqual(String(bytes: message.content, encoding: .utf8)!, "\(content1)\(content2)") 161 | } 162 | else { 163 | XCTFail() 164 | } 165 | } 166 | 167 | static var allTests = [ 168 | ("testMessageBuffer001", testMessageBuffer001), 169 | ("testMessageBuffer002", testMessageBuffer002), 170 | ("testMessageBuffer003", testMessageBuffer003), 171 | ("testMessageBuffer004", testMessageBuffer004), 172 | ("testMessageBuffer005", testMessageBuffer005), 173 | ("testMessageBuffer006", testMessageBuffer006), 174 | ("testMessageBuffer007", testMessageBuffer007), 175 | ] 176 | } 177 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import LanguageServerProtocolTests 3 | @testable import JsonRpcProtocolTests 4 | 5 | XCTMain([ 6 | testCase(MessageBufferTests.allTests), 7 | testCase(EncodableTests.allTests), 8 | ]) 9 | --------------------------------------------------------------------------------