├── .gitignore ├── .travis.yml ├── Fixtures ├── JSON-RPC │ ├── JSON │ │ ├── invalid-header.txt │ │ ├── invalid-json.txt │ │ ├── invalid-request.txt │ │ ├── shutdown.txt │ │ ├── textDocument-didOpen.txt │ │ ├── valid-2.txt │ │ ├── valid-without-header.txt │ │ ├── valid.txt │ │ ├── workspace-didChangeWatchedFiles-changed.json │ │ ├── workspace-didChangeWatchedFiles-created.json │ │ └── workspace-didChangeWatchedFiles-deleted.json │ └── Requests │ │ ├── initialize.txt │ │ ├── multiple-field-header.txt │ │ ├── multiple-requests-and-headers.txt │ │ ├── non-numeric-content-length.txt │ │ ├── partial-request-1.txt │ │ ├── partial-request-2.txt │ │ ├── partial-request-3.txt │ │ ├── partial-request.txt │ │ ├── setTraceNotification.txt │ │ ├── shutdown.txt │ │ ├── textDocument-completion.txt │ │ ├── textDocument-definition.txt │ │ ├── textDocument-didOpen.txt │ │ ├── workspace-didChangeConfiguration.txt │ │ ├── workspace-didChangeWatchedFiles-changed.txt │ │ ├── workspace-didChangeWatchedFiles-created.txt │ │ └── workspace-didChangeWatchedFiles-deleted.txt ├── JSON │ ├── Entities │ │ ├── nested.json │ │ └── single.json │ ├── bar.swift.json │ ├── code-completion │ │ ├── complete.json │ │ ├── constructor.json │ │ ├── import.json │ │ └── integer.json │ ├── cursor │ │ ├── module.json │ │ └── system.json │ └── main.swift.json └── ValidLayouts │ └── Simple │ ├── .build │ ├── debug.yaml │ └── release.yaml │ ├── Package.swift │ ├── README.md │ ├── Sources │ ├── bar.swift │ └── main.swift │ └── Tests │ └── FooTests │ └── FooTests.swift ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── BaseProtocol │ ├── Extensions │ │ └── Collection.swift │ ├── Protocols │ │ └── ServerError.swift │ └── Types │ │ ├── Header.swift │ │ ├── MultibyteDataIterator.swift │ │ ├── PredefinedError.swift │ │ ├── Request.swift │ │ ├── RequestBuffer.swift │ │ ├── Response.swift │ │ └── Result.swift ├── LanguageServer │ ├── Functions │ │ └── handle.swift │ └── main.swift ├── LanguageServerProtocol │ ├── Classes │ │ └── ToolWorkspaceDelegate.swift │ ├── Extenstions │ │ ├── Dictionary.swift │ │ ├── String.swift │ │ └── URL.swift │ ├── Functions │ │ ├── CommonRoot.swift │ │ └── Convert.swift │ ├── Protocols │ │ ├── TextDocumentIdentifier.swift │ │ └── TextDocumentItem.swift │ └── Types │ │ ├── Command.swift │ │ ├── CompletionItem.swift │ │ ├── CompletionItemKind.swift │ │ ├── CompletionOptions.swift │ │ ├── Cursor.swift │ │ ├── DidChangeTextDocumentParams.swift │ │ ├── DidChangeWatchedFilesParams.swift │ │ ├── DidCloseTextDocumentParams.swift │ │ ├── DidOpenTextDocumentParams.swift │ │ ├── FileChangeType.swift │ │ ├── FileEvent.swift │ │ ├── Hover.swift │ │ ├── InitializeParams.swift │ │ ├── InitializeResult.swift │ │ ├── InsertTextFormat.swift │ │ ├── LineCollection.swift │ │ ├── Location.swift │ │ ├── Position.swift │ │ ├── Server.swift │ │ ├── ServerCapabilities.swift │ │ ├── SwiftModule.swift │ │ ├── TextDocument.swift │ │ ├── TextDocumentContentChangeEvent.swift │ │ ├── TextDocumentPositionParams.swift │ │ ├── TextDocumentRange.swift │ │ ├── TextDocumentSyncKind.swift │ │ ├── TextEdit.swift │ │ ├── VersionedTextDocumentIdentifier.swift │ │ ├── WorkspaceError.swift │ │ └── WorkspaceSequence.swift └── SourceKitter │ ├── Extensions │ ├── Array.swift │ ├── Int64.swift │ ├── Optional.swift │ ├── String.swift │ └── URL.swift │ └── Types │ ├── RequestType.swift │ ├── SourceKit.swift │ ├── SourceKitError.swift │ ├── SourceKitRequestable.swift │ ├── library_wrapper.swift │ └── library_wrapper_sourcekitd.swift ├── Tests ├── BaseProtocolTests │ ├── Resources │ │ └── Fixtures.swift │ └── Types │ │ ├── HeaderTests.swift │ │ ├── RequestBufferTests.swift │ │ ├── RequestTests.swift │ │ └── ResponseTests.swift ├── LanguageServerProtocolTests │ ├── Extensions │ │ └── URLTests.swift │ ├── Functions │ │ └── ConvertTests.swift │ ├── Resources │ │ └── Fixtures.swift │ └── Types │ │ ├── CompletionItemTests.swift │ │ ├── CursorTests.swift │ │ ├── DidChangeWatchedFilesParamsTests.swift │ │ ├── LineCollectionTests.swift │ │ ├── SwiftModuleTests.swift │ │ ├── SwiftSourceTests.swift │ │ └── WorkspaceTests.swift └── LinuxMain.swift ├── settings.xcconfig └── sourcery └── LinuxMain.stencil /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | os: 3 | - linux 4 | - osx 5 | language: generic 6 | sudo: required 7 | dist: trusty 8 | osx_image: xcode9.3 9 | install: 10 | - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" 11 | env: 12 | - SWIFT_VERSION=4.1.1 13 | script: 14 | - make ci 15 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/invalid-header.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 2 2 | D: 3 | {} 4 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/invalid-json.txt: -------------------------------------------------------------------------------- 1 | {1} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/invalid-request.txt: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": 1, 4 | "params": "bar" 5 | } 6 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/shutdown.txt: -------------------------------------------------------------------------------- 1 | {"method":"shutdown","params":null,"id":1,"jsonrpc":"2.0"} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/textDocument-didOpen.txt: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","languageId":"swift","version":1,"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet y = Bar(x\nprint(x.y)\n"}}} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/valid-2.txt: -------------------------------------------------------------------------------- 1 | {"jsonrpc": "2.0", "method": "subtract", "params": 3, "id": 1} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/valid-without-header.txt: -------------------------------------------------------------------------------- 1 | { 2 | "jsonrpc": "2.0", 3 | "method": "subtract", 4 | "params": [42, 23], 5 | "id": 1 6 | } 7 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/valid.txt: -------------------------------------------------------------------------------- 1 | {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/workspace-didChangeWatchedFiles-changed.json: -------------------------------------------------------------------------------- 1 | {"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug.yaml","type":2}]} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/workspace-didChangeWatchedFiles-created.json: -------------------------------------------------------------------------------- 1 | {"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release.yaml","type":1}]} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/JSON/workspace-didChangeWatchedFiles-deleted.json: -------------------------------------------------------------------------------- 1 | {"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug.yaml","type":3}]} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/initialize.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 185\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{\"processId\":65017,\"rootPath\":\"/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple\",\"capabilities\":{},\"trace\":\"off\"}} 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/multiple-field-header.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 271 2 | Content-Type: application/vscode-jsonrpc; charset=utf8 3 | Date: Thu, 08 Dec 2016 18:41:04 GMT 4 | 5 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/multiple-requests-and-headers.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 270 2 | 3 | {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","version":6},"contentChanges":[{"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet\nprint(x.y)\n"}]}}Content-Length: 62 4 | 5 | {"jsonrpc":"2.0","method":"$/cancelRequest","params":{"id":1}}Content-Length: 271 6 | 7 | {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","version":7},"contentChanges":[{"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet \nprint(x.y)\n"}]}}Content-Length: 222 8 | 9 | {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"},"position":{"line":1,"character":4}}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/non-numeric-content-length.txt: -------------------------------------------------------------------------------- 1 | Content-Length: ryan 2 | Content-Type: application/vscode-jsonrpc; charset=utf8 3 | Date: Thu, 08 Dec 2016 18:41:04 GMT 4 | 5 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/partial-request-1.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 355 2 | 3 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/partial-request-2.txt: -------------------------------------------------------------------------------- 1 | {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","version":90},"contentChanges":[{"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet y = Bar(dadsfa)\nasdflaskdjfa;\nasdfasdfklja;sdfasdf\nasdfkjasd;fj\nadsfasdjlasd\na\nprint(x.y)\n"}]}}Content-Length: 223 2 | 3 | {"jsonrpc":"2.0","id":13,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"},"position":{"line":6,"character":1}}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/partial-request-3.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 358 2 | 3 | {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","version":93},"contentChanges":[{"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet y = Bar(dadsfa)\nasdflaskdjfa;\nasdfasdfklja;sdfasdf\nasdfkjasd;fj\nadsfasdjlasd\nasdf\nprint(x.y)\n"}]}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/partial-request.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 282 2 | 3 | {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","version":18},"contentChanges":[{"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet y = Bar(x)\nprint(x.y)\n"}]}}Content-Length: 223 4 | 5 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/setTraceNotification.txt: -------------------------------------------------------------------------------- 1 | "Content-Length: 76\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"$/setTraceNotification\",\"params\":{\"value\":\"off\"}}" 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/shutdown.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 58 2 | Content-Type: application/vscode-jsonrpc; charset=utf8 3 | 4 | {"method":"shutdown","params":null,"id":1,"jsonrpc":"2.0"} 5 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/textDocument-completion.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 223 2 | 3 | {"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"},"position":{"line":0,"character":13}}}" -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/textDocument-definition.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 222 2 | 3 | {"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"},"position":{"line":0,"character":9}}} 4 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/textDocument-didOpen.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 278 2 | 3 | {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift","languageId":"swift","version":1,"text":"let x = Bar(x: 1, y: \"Ryan\")\nlet y = Bar(x\nprint(x.y)\n"}}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/workspace-didChangeConfiguration.txt: -------------------------------------------------------------------------------- 1 | "Content-Length: 144\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"workspace/didChangeConfiguration\",\"params\":{\"settings\":{\"swift\":{\"sourceKittenPath\":\"/usr/local/bin/sourcekitten\"}}}}Content-Length: 263\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift\",\"languageId\":\"swift\",\"version\":1,\"text\":\"let x = Bar(x: 1, y: \\\"Ryan\\\")\\nprint(x.y)\\n\"}}}" 2 | -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/workspace-didChangeWatchedFiles-changed.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 192 2 | 3 | {"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug.yaml","type":2}]}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/workspace-didChangeWatchedFiles-created.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 194 2 | 3 | {"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release.yaml","type":1}]}} -------------------------------------------------------------------------------- /Fixtures/JSON-RPC/Requests/workspace-didChangeWatchedFiles-deleted.txt: -------------------------------------------------------------------------------- 1 | Content-Length: 192 2 | 3 | {"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug.yaml","type":3}]}} -------------------------------------------------------------------------------- /Fixtures/JSON/Entities/nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.line": 1, 3 | "key.name": "Bar", 4 | "key.usr": "s:V4main3Bar", 5 | "key.column": 8, 6 | "key.kind": "source.lang.swift.decl.struct", 7 | "key.entities": [ 8 | { 9 | "key.line": 2, 10 | "key.name": "x", 11 | "key.usr": "s:vV4main3Bar1xSi", 12 | "key.column": 9, 13 | "key.kind": "source.lang.swift.decl.var.instance" 14 | }, 15 | { 16 | "key.line": 2, 17 | "key.name": "Int", 18 | "key.usr": "s:Si", 19 | "key.column": 12, 20 | "key.kind": "source.lang.swift.ref.struct" 21 | }, 22 | { 23 | "key.line": 3, 24 | "key.name": "y", 25 | "key.usr": "s:vV4main3Bar1ySS", 26 | "key.column": 9, 27 | "key.kind": "source.lang.swift.decl.var.instance" 28 | }, 29 | { 30 | "key.line": 3, 31 | "key.name": "String", 32 | "key.usr": "s:SS", 33 | "key.column": 12, 34 | "key.kind": "source.lang.swift.ref.struct" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /Fixtures/JSON/Entities/single.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.line": 2, 3 | "key.name": "x", 4 | "key.usr": "s:vV4main3Bar1xSi", 5 | "key.column": 9, 6 | "key.kind": "source.lang.swift.decl.var.instance" 7 | } -------------------------------------------------------------------------------- /Fixtures/JSON/bar.swift.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.hash": "2AKPUN8GGDH2G", 3 | "key.entities": [ 4 | { 5 | "key.line": 1, 6 | "key.name": "Bar", 7 | "key.usr": "s:V4main3Bar", 8 | "key.column": 8, 9 | "key.kind": "source.lang.swift.decl.struct", 10 | "key.entities": [ 11 | { 12 | "key.line": 2, 13 | "key.name": "x", 14 | "key.usr": "s:vV4main3Bar1xSi", 15 | "key.column": 9, 16 | "key.kind": "source.lang.swift.decl.var.instance" 17 | }, 18 | { 19 | "key.line": 2, 20 | "key.name": "Int", 21 | "key.usr": "s:Si", 22 | "key.column": 12, 23 | "key.kind": "source.lang.swift.ref.struct" 24 | }, 25 | { 26 | "key.line": 3, 27 | "key.name": "y", 28 | "key.usr": "s:vV4main3Bar1ySS", 29 | "key.column": 9, 30 | "key.kind": "source.lang.swift.decl.var.instance" 31 | }, 32 | { 33 | "key.line": 3, 34 | "key.name": "String", 35 | "key.usr": "s:SS", 36 | "key.column": 12, 37 | "key.kind": "source.lang.swift.ref.struct" 38 | } 39 | ] 40 | }, 41 | { 42 | "key.line": 6, 43 | "key.name": "Bar", 44 | "key.usr": "s:V4main3Bar", 45 | "key.column": 11, 46 | "key.kind": "source.lang.swift.decl.extension.struct", 47 | "key.entities": [ 48 | { 49 | "key.line": 6, 50 | "key.name": "Bar", 51 | "key.usr": "s:V4main3Bar", 52 | "key.column": 11, 53 | "key.kind": "source.lang.swift.ref.struct" 54 | }, 55 | { 56 | "key.line": 7, 57 | "key.name": "foo()", 58 | "key.usr": "s:FV4main3Bar3fooFT_Si", 59 | "key.column": 10, 60 | "key.kind": "source.lang.swift.decl.function.method.instance", 61 | "key.entities": [ 62 | { 63 | "key.line": 7, 64 | "key.name": "Int", 65 | "key.usr": "s:Si", 66 | "key.column": 19, 67 | "key.kind": "source.lang.swift.ref.struct" 68 | } 69 | ] 70 | } 71 | ] 72 | }, 73 | { 74 | "key.line": 12, 75 | "key.name": "Blah", 76 | "key.usr": "s:V4mainP33_49CF099D6D8B13348A89A7583D18FBF54Blah", 77 | "key.column": 16, 78 | "key.kind": "source.lang.swift.decl.struct", 79 | "key.entities": [ 80 | { 81 | "key.line": 13, 82 | "key.name": "x", 83 | "key.usr": "s:vV4mainP33_49CF099D6D8B13348A89A7583D18FBF54Blah1xVS_3Bar", 84 | "key.column": 9, 85 | "key.kind": "source.lang.swift.decl.var.instance" 86 | }, 87 | { 88 | "key.line": 13, 89 | "key.name": "Bar", 90 | "key.usr": "s:V4main3Bar", 91 | "key.column": 12, 92 | "key.kind": "source.lang.swift.ref.struct" 93 | } 94 | ] 95 | } 96 | ], 97 | "key.dependencies": [ 98 | { 99 | "key.hash": "1R3QYS5UIUK35", 100 | "key.name": "Swift", 101 | "key.filepath": "\/Applications\/Xcode-beta.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/x86_64\/Swift.swiftmodule", 102 | "key.kind": "source.lang.swift.import.module.swift", 103 | "key.is_system": true 104 | } 105 | ] 106 | } -------------------------------------------------------------------------------- /Fixtures/JSON/code-completion/constructor.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.kind": "source.lang.swift.decl.function.constructor", 3 | "key.name": "x:y:)", 4 | "key.sourcetext": "x: <#T##Int#>, y: <#T##String#>)", 5 | "key.description": "(x: Int, y: String)", 6 | "key.typename": "Bar", 7 | "key.modulename": "Test", 8 | "key.context": "source.codecompletion.context.thisclass", 9 | "key.num_bytes_to_erase": 0, 10 | "key.associated_usrs": "s:FV4Test3BarcFT1xSi1ySS_S0_" 11 | } 12 | -------------------------------------------------------------------------------- /Fixtures/JSON/code-completion/import.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.context" : "source.codecompletion.context.none", 3 | "key.description" : "import", 4 | "key.sourcetext" : "import", 5 | "key.typename" : "", 6 | "key.name" : "import", 7 | "key.num_bytes_to_erase" : 0, 8 | "key.kind" : "source.lang.swift.keyword" 9 | } 10 | -------------------------------------------------------------------------------- /Fixtures/JSON/code-completion/integer.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.modulename" : "Swift", 3 | "key.doc.brief" : "A set of common requirements for Swift’s integer types.", 4 | "key.associated_usrs" : "s:Ps7Integer", 5 | "key.description" : "Integer", 6 | "key.sourcetext" : "Integer", 7 | "key.typename" : "Integer", 8 | "key.context" : "source.codecompletion.context.othermodule", 9 | "key.name" : "Integer", 10 | "key.num_bytes_to_erase" : 0, 11 | "key.kind" : "source.lang.swift.decl.protocol" 12 | } 13 | -------------------------------------------------------------------------------- /Fixtures/JSON/cursor/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.filepath" : "\/Users\/ryan\/Source\/langserver-swift\/Fixtures\/ValidLayouts\/Simple\/Sources\/main.swift", 3 | "key.annotated_decl" : "func dumpThem(_ all: Any...)<\/Declaration>", 4 | "key.fully_annotated_decl" : "func<\/syntaxtype.keyword> dumpThem<\/decl.name>(_<\/decl.var.parameter.argument_label> all<\/decl.var.parameter.name>: Any<\/decl.var.parameter.type>...<\/decl.var.parameter>)<\/decl.function.free>", 5 | "key.typename" : "(Any...) -> ()", 6 | "key.length" : 23, 7 | "key.name" : "dumpThem(_:)", 8 | "key.typeusr" : "_TtFtGSaP___T_", 9 | "key.usr" : "s:F3bar8dumpThemFtGSaP___T_", 10 | "key.kind" : "source.lang.swift.decl.function.free", 11 | "key.offset" : 5 12 | } -------------------------------------------------------------------------------- /Fixtures/JSON/main.swift.json: -------------------------------------------------------------------------------- 1 | { 2 | "key.hash": "1UO7GJT73BI7M", 3 | "key.entities": [ 4 | { 5 | "key.line": 1, 6 | "key.name": "Bar", 7 | "key.usr": "s:V4main3Bar", 8 | "key.column": 8, 9 | "key.kind": "source.lang.swift.decl.struct", 10 | "key.entities": [ 11 | { 12 | "key.line": 2, 13 | "key.name": "x", 14 | "key.usr": "s:vV4main3Bar1xSi", 15 | "key.column": 9, 16 | "key.kind": "source.lang.swift.decl.var.instance" 17 | }, 18 | { 19 | "key.line": 2, 20 | "key.name": "Int", 21 | "key.usr": "s:Si", 22 | "key.column": 12, 23 | "key.kind": "source.lang.swift.ref.struct" 24 | }, 25 | { 26 | "key.line": 3, 27 | "key.name": "y", 28 | "key.usr": "s:vV4main3Bar1ySS", 29 | "key.column": 9, 30 | "key.kind": "source.lang.swift.decl.var.instance" 31 | }, 32 | { 33 | "key.line": 3, 34 | "key.name": "String", 35 | "key.usr": "s:SS", 36 | "key.column": 12, 37 | "key.kind": "source.lang.swift.ref.struct" 38 | } 39 | ] 40 | }, 41 | { 42 | "key.line": 6, 43 | "key.name": "Bar", 44 | "key.usr": "s:V4main3Bar", 45 | "key.column": 11, 46 | "key.kind": "source.lang.swift.decl.extension.struct", 47 | "key.entities": [ 48 | { 49 | "key.line": 6, 50 | "key.name": "Bar", 51 | "key.usr": "s:V4main3Bar", 52 | "key.column": 11, 53 | "key.kind": "source.lang.swift.ref.struct" 54 | }, 55 | { 56 | "key.line": 7, 57 | "key.name": "foo()", 58 | "key.usr": "s:FV4main3Bar3fooFT_Si", 59 | "key.column": 10, 60 | "key.kind": "source.lang.swift.decl.function.method.instance", 61 | "key.entities": [ 62 | { 63 | "key.line": 7, 64 | "key.name": "Int", 65 | "key.usr": "s:Si", 66 | "key.column": 19, 67 | "key.kind": "source.lang.swift.ref.struct" 68 | } 69 | ] 70 | } 71 | ] 72 | }, 73 | { 74 | "key.line": 12, 75 | "key.name": "Blah", 76 | "key.usr": "s:V4mainP33_49CF099D6D8B13348A89A7583D18FBF54Blah", 77 | "key.column": 16, 78 | "key.kind": "source.lang.swift.decl.struct", 79 | "key.entities": [ 80 | { 81 | "key.line": 13, 82 | "key.name": "x", 83 | "key.usr": "s:vV4mainP33_49CF099D6D8B13348A89A7583D18FBF54Blah1xVS_3Bar", 84 | "key.column": 9, 85 | "key.kind": "source.lang.swift.decl.var.instance" 86 | }, 87 | { 88 | "key.line": 13, 89 | "key.name": "Bar", 90 | "key.usr": "s:V4main3Bar", 91 | "key.column": 12, 92 | "key.kind": "source.lang.swift.ref.struct" 93 | } 94 | ] 95 | } 96 | ], 97 | "key.dependencies": [ 98 | { 99 | "key.hash": "1YNOQTS69N574", 100 | "key.name": "Swift", 101 | "key.filepath": "\/Applications\/Xcode.app\/Contents\/Developer\/Toolchains\/XcodeDefault.xctoolchain\/usr\/lib\/swift\/macosx\/x86_64\/Swift.swiftmodule", 102 | "key.kind": "source.lang.swift.import.module.swift", 103 | "key.is_system": true 104 | } 105 | ] 106 | } -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/.build/debug.yaml: -------------------------------------------------------------------------------- 1 | client: 2 | name: swift-build 3 | tools: {} 4 | targets: 5 | "test": ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.swiftmodule","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple"] 6 | "main": ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.swiftmodule","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple"] 7 | default: "main" 8 | commands: 9 | "": 10 | tool: swift-compiler 11 | executable: "/Users/ryan/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-12-13-a.xctoolchain/usr/bin/swiftc" 12 | module-name: "Simple" 13 | module-output-path: "/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.swiftmodule" 14 | inputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/bar.swift","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"] 15 | outputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.swiftmodule"] 16 | import-paths: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug"] 17 | temps-path: "/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build" 18 | objects: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o"] 19 | other-args: ["-j8","-D","SWIFT_PACKAGE","-Onone","-g","-enable-testing","-F","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks","-target","x86_64-apple-macosx10.10","-sdk","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk","-module-cache-path","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/ModuleCache"] 20 | sources: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/bar.swift","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"] 21 | is-library: false 22 | enable-whole-module-optimization: false 23 | num-threads: "8" 24 | 25 | "": 26 | tool: shell 27 | description: "Linking ./.build/debug/Simple" 28 | inputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o"] 29 | outputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple"] 30 | args: ["/Users/ryan/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-12-13-a.xctoolchain/usr/bin/swiftc","-target","x86_64-apple-macosx10.10","-sdk","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk","-g","-L/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug","-o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple","-F","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks","-emit-executable","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/debug/Simple.build/main.swift.o"] 31 | 32 | -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/.build/release.yaml: -------------------------------------------------------------------------------- 1 | client: 2 | name: swift-build 3 | tools: {} 4 | targets: 5 | "test": ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.swiftmodule","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple"] 6 | "main": ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.swiftmodule","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple"] 7 | default: "main" 8 | commands: 9 | "": 10 | tool: swift-compiler 11 | executable: "/Users/ryan/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-12-13-a.xctoolchain/usr/bin/swiftc" 12 | module-name: "Simple" 13 | module-output-path: "/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.swiftmodule" 14 | inputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/bar.swift","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"] 15 | outputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.swiftmodule"] 16 | import-paths: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release"] 17 | temps-path: "/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build" 18 | objects: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o"] 19 | other-args: ["-j8","-D","SWIFT_PACKAGE","-O","-F","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks","-target","x86_64-apple-macosx10.10","-sdk","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk","-module-cache-path","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/ModuleCache"] 20 | sources: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/bar.swift","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift"] 21 | is-library: false 22 | enable-whole-module-optimization: true 23 | num-threads: "8" 24 | 25 | "": 26 | tool: shell 27 | description: "Linking ./.build/release/Simple" 28 | inputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o"] 29 | outputs: ["/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple"] 30 | args: ["/Users/ryan/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-12-13-a.xctoolchain/usr/bin/swiftc","-target","x86_64-apple-macosx10.10","-sdk","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk","-L/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release","-o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple","-F","/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks","-emit-executable","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/bar.swift.o","/Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/.build/release/Simple.build/main.swift.o"] 31 | 32 | -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "Test" 5 | ) 6 | -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLovelett/langserver-swift/4ed2cbedae1264f2739abe78b6b537b6517652f8/Fixtures/ValidLayouts/Simple/README.md -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/Sources/bar.swift: -------------------------------------------------------------------------------- 1 | struct Bar { 2 | let x: Int 3 | let y: String 4 | } 5 | 6 | extension Bar { 7 | func foo() -> Int { 8 | return 1 9 | } 10 | } 11 | 12 | private struct Blah { 13 | let x: Bar 14 | } 15 | -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/Sources/main.swift: -------------------------------------------------------------------------------- 1 | let x = Bar(x: 1, y: "Ryan") 2 | print(x.y) -------------------------------------------------------------------------------- /Fixtures/ValidLayouts/Simple/Tests/FooTests/FooTests.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RLovelett/langserver-swift/4ed2cbedae1264f2739abe78b6b537b6517652f8/Fixtures/ValidLayouts/Simple/Tests/FooTests/FooTests.swift -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Ryan Lovelett 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | 3 | ifeq ($(UNAME), Linux) 4 | DefaultBuildFlags= 5 | else ifeq ($(UNAME), Darwin) 6 | DefaultBuildFlags=-Xswiftc -target -Xswiftc x86_64-apple-macosx10.12 7 | endif 8 | DebugBuildFlags=$(DefaultBuildFlags) 9 | ReleaseBuildFlags=$(DefaultBuildFlags) -Xswiftc -static-stdlib -c release 10 | INSTALL_PATH?=/usr/local 11 | 12 | .PHONY: debug 13 | ## Build the package in debug 14 | debug: 15 | swift build $(DebugBuildFlags) 16 | 17 | .PHONY: release 18 | ## Build the package in release 19 | release: 20 | swift build $(ReleaseBuildFlags) 21 | 22 | .PHONY: clean 23 | ## Clean the package of build information 24 | clean: 25 | rm -rf .build 26 | 27 | .PHONY: install 28 | ## Install the release version of the package 29 | install: release 30 | cp -f .build/release/langserver-swift $(INSTALL_PATH)/bin/langserver-swift 31 | 32 | .PHONY: uninstall 33 | ## Undo the effects of install 34 | uninstall: 35 | rm -r $(INSTALL_PATH)/bin/langserver-swift 36 | 37 | .PHONY: test 38 | ## Build and run the tests 39 | test: 40 | swift test $(DebugBuildFlags) 41 | 42 | .PHONY: xcodeproj 43 | ## Generate the Xcode project 44 | xcodeproj: 45 | swift package generate-xcodeproj --enable-code-coverage --xcconfig-overrides settings.xcconfig 46 | 47 | .PHONY: print_target_build_dir 48 | ## Print Xcode project's TARGET_BUILD_DIR value to use for debugging purposes 49 | print_target_build_dir: xcodeproj 50 | xcodebuild -project langserver-swift.xcodeproj -target "LanguageServer" -showBuildSettings | grep "TARGET_BUILD_DIR" 51 | 52 | .PHONY: ci 53 | ## Operations to run for Continuous Integration 54 | ifeq ($(UNAME), Linux) 55 | ci: tools_versions debug test 56 | else ifeq ($(UNAME), Darwin) 57 | ci: tools_versions debug test release 58 | endif 59 | 60 | .PHONY: tools_versions 61 | ## Print the tools versions to STDOUT 62 | tools_versions: 63 | swift --version 64 | swift build --version 65 | 66 | .PHONY: help 67 | # taken from this gist https://gist.github.com/rcmachado/af3db315e31383502660 68 | ## Show this help message. 69 | help: 70 | $(info Usage: make [target...]) 71 | $(info Available targets) 72 | @awk '/^[a-zA-Z\-\_0-9]+:/ { \ 73 | nb = sub( /^## /, "", helpMsg ); \ 74 | if(nb == 0) { \ 75 | helpMsg = $$0; \ 76 | nb = sub( /^[^:]*:.* ## /, "", helpMsg ); \ 77 | } \ 78 | if (nb) \ 79 | print $$1 "\t" helpMsg; \ 80 | } \ 81 | { helpMsg = $$0 }' \ 82 | $(MAKEFILE_LIST) | column -ts $$'\t' | \ 83 | grep --color '^[^ ]*' 84 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Argo", 6 | "repositoryURL": "https://github.com/RLovelett/Argo.git", 7 | "state": { 8 | "branch": "swift-4.1-macOS-and-linux", 9 | "revision": "a3c76f404d2e5d9bf49206b1b7225ada31c24e26", 10 | "version": null 11 | } 12 | }, 13 | { 14 | "package": "Curry", 15 | "repositoryURL": "https://github.com/thoughtbot/Curry.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "b6bf27ec9d711f607a8c7da9ca69ee9eaa201a22", 19 | "version": "4.0.1" 20 | } 21 | }, 22 | { 23 | "package": "Ogra", 24 | "repositoryURL": "https://github.com/RLovelett/Ogra.git", 25 | "state": { 26 | "branch": "master", 27 | "revision": "2557450baad23d299fd4d90ba5f0b16e36203040", 28 | "version": null 29 | } 30 | }, 31 | { 32 | "package": "Runes", 33 | "repositoryURL": "https://github.com/thoughtbot/Runes.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "30701f9587e31eca105318a5612691bf35a95d0b", 37 | "version": "4.1.1" 38 | } 39 | }, 40 | { 41 | "package": "SourceKit", 42 | "repositoryURL": "https://github.com/RLovelett/SourceKit.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "5e4872713081cf104a74a1f53be64418ff75fb47", 46 | "version": "1.0.2" 47 | } 48 | }, 49 | { 50 | "package": "SwiftPM", 51 | "repositoryURL": "https://github.com/RLovelett/swift-package-manager.git", 52 | "state": { 53 | "branch": "swift-4.1-branch", 54 | "revision": "717de5e5a534e50fe176a510d7502b63b4901678", 55 | "version": null 56 | } 57 | }, 58 | { 59 | "package": "SWXMLHash", 60 | "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "2211b35c2e0e8b08493f86ba52b26e530cabb751", 64 | "version": "4.7.0" 65 | } 66 | } 67 | ] 68 | }, 69 | "version": 1 70 | } 71 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "langserver-swift", 8 | products: [ 9 | .executable( 10 | name: "langserver-swift", 11 | targets: [ 12 | "BaseProtocol", 13 | "SourceKitter", 14 | "LanguageServerProtocol", 15 | "LanguageServer" 16 | ] 17 | ) 18 | ], 19 | dependencies: [ 20 | .package(url: "https://github.com/RLovelett/SourceKit.git", from: "1.0.0"), 21 | .package(url: "https://github.com/RLovelett/Argo.git", .branch("swift-4.1-macOS-and-linux")), 22 | .package(url: "https://github.com/RLovelett/Ogra.git", .branch("master")), 23 | .package(url: "https://github.com/thoughtbot/Curry.git", from: "4.0.0"), 24 | .package(url: "https://github.com/RLovelett/swift-package-manager.git", .branch("swift-4.1-branch")), 25 | .package(url: "https://github.com/drmohundro/SWXMLHash.git", from: "4.0.0"), 26 | ], 27 | targets: [ 28 | .target( 29 | name: "BaseProtocol", 30 | dependencies: [ 31 | "Argo", 32 | "Curry", 33 | "Ogra", 34 | ] 35 | ), 36 | .target( 37 | name: "SourceKitter", 38 | dependencies: [ 39 | "Argo", 40 | ] 41 | ), 42 | .target( 43 | name: "LanguageServerProtocol", 44 | dependencies: [ 45 | "BaseProtocol", 46 | "SourceKitter", 47 | "SwiftPM", 48 | "SWXMLHash", 49 | ] 50 | ), 51 | .target( 52 | name: "LanguageServer", 53 | dependencies: [ 54 | "BaseProtocol", 55 | "LanguageServerProtocol", 56 | ] 57 | ), 58 | .testTarget( 59 | name: "BaseProtocolTests", 60 | dependencies: [ 61 | "BaseProtocol" 62 | ] 63 | ), 64 | .testTarget( 65 | name: "LanguageServerProtocolTests", 66 | dependencies: [ 67 | "LanguageServerProtocol" 68 | ] 69 | ), 70 | ] 71 | ) 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Language Server 2 | 3 | ![macOS](https://img.shields.io/badge/os-macOS-green.svg?style=flat) 4 | ![Linux](https://img.shields.io/badge/os-linux-green.svg?style=flat) 5 | ![Apache 2](https://img.shields.io/badge/license-Apache2-blue.svg?style=flat) 6 | ![](https://img.shields.io/badge/Swift-4.1.0-orange.svg?style=flat) 7 | [![Build Status](https://travis-ci.org/RLovelett/langserver-swift.svg?branch=master)](https://travis-ci.org/RLovelett/langserver-swift) 8 | [![Join the chat at https://gitter.im/langserver-swift](https://badges.gitter.im/langserver-swift/Lobby.svg)](https://gitter.im/langserver-swift?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 9 | 10 | # Overview 11 | 12 | A Swift implementation of the open [Language Server Protocol](https://github.com/Microsoft/language-server-protocol). The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, goto definition, find all references and alike into the tool. 13 | 14 | Currently this implementation is used by [Swift for Visual Studio Code](https://github.com/RLovelett/vscode-swift). 15 | 16 | # Prerequisites 17 | 18 | ## Swift 19 | 20 | * Swift version 4.1.0 21 | * The toolchain that comes with Xcode Version 9.3 (9E145) (`Apple Swift version 4.1 (swiftlang-902.0.48 clang-902.0.37.1)`) 22 | 23 | ## macOS 24 | 25 | * macOS 10.12 (*Sierra*) or higher 26 | 27 | ## Linux 28 | 29 | * **Coming Soon** 30 | 31 | # Build 32 | 33 | ``` 34 | % cd 35 | % make debug 36 | ``` 37 | 38 | or with Xcode 39 | 40 | ``` 41 | % cd 42 | % make xcodeproj 43 | ``` 44 | 45 | # Test 46 | 47 | ``` 48 | % cd 49 | % make test 50 | ``` 51 | # Debug and Development 52 | 53 | The language server itself relies on a language server client to interact with it. [This server has been developed to work with Visual Studio Code](https://github.com/RLovelett/vscode-swift). Though it should be noted that any client that implements the protocol _should_ work and is thusly supported. 54 | 55 | An example workflow for interactively debugging the language server while using it with the Visual Stuio Code client is provided in this section. The instructions are devided into two sections. The first section explains how to generate and configure an Xcode project for debugging. The second section explains how to configure the Visual Studio Code plugin to use the debug executable. 56 | 57 | ## Xcode (e.g., [langserver-swift](https://github.com/RLovelett/langserver-swift)) 58 | 59 | In the directory containing the clone of this repository use SwiftPM to generate an Xcode project. 60 | 61 | ``` 62 | % git clone https://github.com/RLovelett/langserver-swift.git 63 | % cd langserver-swift 64 | % make xcodeproj 65 | ``` 66 | 67 | Since the language server client, e.g., VSCode, will actually launch the language server LLDB needs to be told to wait for the application to launch. This can be configured in Xcode after opening the generated project in Xcode. See the screenshot below. 68 | 69 | screen shot 2017-02-22 at 8 55 57 am 70 | 71 | The next step is to build the executable and launch LLDB. Both of these steps can be performed by going to "Product > Run" or the keyboard shortcut ⌘R. After building completes, Xcode should report something like "Waiting to attach to LanguageServer : LanguageServer". 72 | 73 | screen shot 2017-02-22 at 9 40 33 am 74 | 75 | One final step is to determine the `TARGET_BUILD_DIR`. This is used to tell the VSCode extension in the next section where the debug language server is located. 76 | 77 | From a terminal whose current working directory contains the Xcode project previously generated by SwiftPM you can get this information from `xcodebuild`. 78 | 79 | ``` 80 | % xcodebuild -project langserver-swift.xcodeproj -target "LanguageServer" -showBuildSettings | grep "TARGET_BUILD_DIR" 81 | TARGET_BUILD_DIR = /Users/ryan/Library/Developer/Xcode/DerivedData/langserver-swift-gellhgzzpradfqbgjnbtkvzjqymv/Build/Products/Debug 82 | ``` 83 | 84 | Or using `make`: 85 | 86 | ``` 87 | % make print_target_build_dir 88 | ``` 89 | 90 | Take note of this value it will be used later. 91 | 92 | # VSCode (e.g., [vscode-swift](https://github.com/RLovelett/vscode-swift)) 93 | 94 | Open the directory containing the clone of the Visual Studio Code extension in Visual Studio Code. 95 | 96 | ``` 97 | % git clone https://github.com/RLovelett/vscode-swift.git 98 | % code . 99 | ``` 100 | 101 | Start the TypeScript compiler or the build task (e.g., ⇧⌘B or Tasks: Run Build Task). 102 | 103 | Now open `src/extension.ts` and provide the value of `TARGET_BUILD_DIR` for the debug executable. The change should be similar to the patch that follows. 104 | 105 | ``` 106 | diff --git a/src/extension.ts b/src/extension.ts 107 | index b5ad751..7970ae1 100644 108 | --- a/src/extension.ts 109 | +++ b/src/extension.ts 110 | @@ -13,7 +13,7 @@ export function activate(context: ExtensionContext) { 111 | .get("languageServerPath", "/usr/local/bin/LanguageServer"); 112 | 113 | let run: Executable = { command: executableCommand }; 114 | - let debug: Executable = run; 115 | + let debug: Executable = { command: "${TARGET_BUILD_DIR}/LanguageServer" }; 116 | let serverOptions: ServerOptions = { 117 | run: run, 118 | debug: debug 119 | ``` 120 | 121 | **NOTE:** Make sure the `${TARGET_BUILD_DIR}` is populated with the value you generated in the Xcode section. It is not an environment variable so that will not be evaluated. 122 | 123 | Once this is complete you should be able to open the VSCode debugger and and select `Launch Extension`. This should start both the language server (Xcode/Swift) and the extension (VScode/TypeScript) in debug mode. 124 | 125 | # Caveats 126 | 127 | 1. As noted above you might not be able to capture all the commands upon the language server initially starting up. The current hypothesis is that it takes a little bit of time for LLDB (the Swift debugger) to actually attach to the running process so a few instructions are missed. 128 | 129 | One recommendation is to put a break-point in [`handle.swift`](https://github.com/RLovelett/langserver-swift/blob/251641da96ac1e0ae90f0ead3aa2f210fcb2c599/Sources/LanguageServer/Functions/handle.swift#L17) as this is likely where the server is getting into to trouble. 130 | 131 | 2. Messages are logged to the `Console.app` using the `me.lovelett.langserver-swift` sub-system. One place to look the raw language server JSON-RPC messages is there. 132 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Extensions/Collection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/3/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | public extension Collection where Iterator.Element: Ogra.Encodable { 13 | 14 | func encode() -> JSON { 15 | let arr = self.map({ $0.encode() }) 16 | return JSON.array(arr) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Protocols/ServerError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerError.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | public protocol ServerError : Error { 10 | var code: Int { get } 11 | var message: String { get } 12 | var data: Any? { get } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Types/Header.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Header.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/8/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | private let terminator = Data(bytes: [0x0D, 0x0A]) // "\r\n" 12 | private let separatorBytes = Data(bytes: [0x3A, 0x20]) // ": " 13 | 14 | /// The `Header` provides an iterator interface to the header part of the base protocol of language 15 | /// server protocol. 16 | /// 17 | /// Communications between the server and the client in the language server protocol consist of a header (comparable to 18 | /// HTTP) and a content part (which conforms to [JSON-RPC](http://www.jsonrpc.org)). 19 | struct Header : IteratorProtocol { 20 | 21 | /// Each `Header.Field` can be viewed as a single, logical line of ASCII characters, comprising a field-name and a 22 | /// field-body. 23 | /// 24 | /// - Warning: While `Header`, `Fields` and `Field` strive to be spec compliant they may not actually be. Any 25 | /// any discrepancies should be reported. 26 | /// 27 | /// - SeeAlso: [RFC 822 Section 3.1](http://www.ietf.org/rfc/rfc0822.txt) 28 | /// - SeeAlso: [RFC 7230, Section 3.2 - Header Fields](https://tools.ietf.org/html/rfc7230#section-3.2) 29 | struct Field { 30 | 31 | /// The name of a field in a header. 32 | let name: String 33 | 34 | /// The body, or value, of a field in a header. 35 | let body: String 36 | 37 | /// Initialize a `Field` by parsing the byte buffer. If the buffer does not conform to the specification then 38 | /// the `Field` will fail to initialize. 39 | /// 40 | /// Where possible the data should conform to RFC 7230, Section 3.2 - Header Fields. 41 | /// 42 | /// - Parameter data: A byte buffer that contains a RFC 7230 header field. 43 | /// - SeeAlso: [RFC 7230, Section 3.2 - Header Fields](https://tools.ietf.org/html/rfc7230#section-3.2) 44 | init?(_ data: Data) { 45 | guard let seperator = data.range(of: separatorBytes) else { return nil } 46 | guard let n = String(data: data.subdata(in: data.startIndex.. { 69 | return index.. Header.Field? { 76 | let foo = header.range(of: terminator, options: [], in: range) ?? (header.endIndex..(index.. Data.Index? 18 | 19 | init(split: inout Data, onSeparator: Data, calculateIndex: @escaping (Data) -> Data.Index?) { 20 | separator = onSeparator 21 | data = split 22 | calculateNewIndex = calculateIndex 23 | } 24 | 25 | mutating func append(_ data: Data) { 26 | self.data.append(data) 27 | } 28 | 29 | } 30 | 31 | extension MultibyteDataIterator : IteratorProtocol { 32 | 33 | mutating func next() -> Data? { 34 | guard !data.isEmpty else { return nil } 35 | let separatorRange = data.range(of: separator) ?? (data.endIndex..(data.startIndex..(separatorRange.upperBound.. = json["method"] 93 | let dId: Decoded = json["id"] 94 | // A Structured value that holds the parameter values to be used during the invocation of 95 | // the method. This member MAY be omitted. 96 | let dParams: Decoded = json["params"] <|> pure(.null) 97 | 98 | switch (dMethod, dId, dParams) { 99 | case (.success(let m), .success(let i), .success(let j)): 100 | self = .request(id: i, method: m, params: j) 101 | case (.success(let m), _, .success(let j)): 102 | self = .notification(method: m, params: j) 103 | default: 104 | throw PredefinedError.invalidRequest 105 | } 106 | } 107 | 108 | public func parse() throws -> T where T.DecodedType == T { 109 | switch T.decode(params) { 110 | case .success(let p): 111 | return p 112 | case .failure(_): 113 | throw PredefinedError.invalidParams 114 | } 115 | } 116 | } 117 | 118 | extension Request.Identifier : Argo.Decodable { 119 | 120 | public static func decode(_ json: JSON) -> Decoded { 121 | switch json { 122 | case .number(let x): 123 | return .success(.number(x.intValue)) 124 | case .string(let s): 125 | return .success(.string(s)) 126 | default: 127 | return .typeMismatch(expected: "String or Number", actual: json) 128 | } 129 | } 130 | 131 | } 132 | 133 | extension Request.Identifier : Equatable { 134 | 135 | /// Returns a Boolean value indicating whether two values are equal. 136 | /// 137 | /// Equality is the inverse of inequality. For any values `a` and `b`, 138 | /// `a == b` implies that `a != b` is `false`. 139 | /// 140 | /// - Parameters: 141 | /// - lhs: A value to compare. 142 | /// - rhs: Another value to compare. 143 | public static func ==(lhs: Request.Identifier, rhs: Request.Identifier) -> Bool { 144 | switch (lhs, rhs) { 145 | case (.string(let lhss), .string(let rhss)) where lhss == rhss: 146 | return true 147 | case (.number(let lhsn), .number(let rhsn)) where lhsn == rhsn: 148 | return true 149 | default: 150 | return false 151 | } 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Types/RequestBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestBuffer.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/8/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 11 | import os.log 12 | 13 | private let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "RequestBuffer") 14 | #endif 15 | private let terminatorPattern = Data(bytes: [0x0D, 0x0A, 0x0D, 0x0A]) // "\r\n\r\n" 16 | 17 | public class RequestBuffer { 18 | 19 | fileprivate var buffer: Data 20 | 21 | public init(_ data: Data? = nil) { 22 | buffer = data ?? Data() 23 | } 24 | 25 | public func append(_ data: Data) { 26 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 27 | os_log("Adding %{iec-bytes}d to the request buffer which has %{iec-bytes}d", log: log, type: .default, data.count, buffer.count) 28 | #endif 29 | buffer.append(data) 30 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 31 | if let new = String(bytes: data, encoding: .utf8), let buffer = String(bytes: buffer, encoding: .utf8) { 32 | os_log("Added: %{public}@", log: log, type: .default, new) 33 | os_log("Buffer: %{public}@", log: log, type: .default, buffer) 34 | } 35 | #endif 36 | } 37 | 38 | public func append(_ data: DispatchData) { 39 | data.withUnsafeBytes { [count = data.count] in 40 | buffer.append($0, count: count) 41 | } 42 | } 43 | 44 | } 45 | 46 | extension RequestBuffer : IteratorProtocol { 47 | 48 | public func next() -> Data? { 49 | guard !buffer.isEmpty else { return nil } 50 | let separatorRange = buffer.range(of: terminatorPattern) ?? (buffer.endIndex..(buffer.startIndex..(separatorRange.upperBound.. RequestBuffer { 72 | return self 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Types/Response.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Response.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | import Ogra 12 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 13 | import os.log 14 | #endif 15 | 16 | fileprivate let headerSeparator = "\r\n" 17 | fileprivate let headerTerminator = "\r\n\r\n" 18 | fileprivate let pattern = headerTerminator.data(using: .utf8)! 19 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 20 | private let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "Response") 21 | #endif 22 | 23 | public struct Response { 24 | 25 | let json: JSON 26 | 27 | public init(to request: Request, is message: Ogra.Encodable) { 28 | switch request { 29 | case .notification(method: _, params: _): 30 | json = .null 31 | case .request(id: let id, method: _, params: _): 32 | self = Response(is: .success(message), for: id) 33 | } 34 | } 35 | 36 | public init(to request: Request, is error: ServerError) { 37 | switch request { 38 | case .notification(method: _, params: _): 39 | json = .null 40 | case .request(id: let id, method: _, params: _): 41 | self = Response(is: .error(error), for: id) 42 | } 43 | } 44 | 45 | public init(to request: Request, is message: C) where C.Iterator.Element: Ogra.Encodable { 46 | switch request { 47 | case .notification(_, _): 48 | json = .null 49 | case .request(id: let id, _, _): 50 | self = Response(is: .success(message.encode()), for: id) 51 | } 52 | } 53 | 54 | private init(is result: Result, for id: Request.Identifier) { 55 | var obj: [String : JSON] = [ 56 | "jsonrpc" : JSON.string("2.0") 57 | ] 58 | 59 | switch id { 60 | case .number(let val): obj["id"] = JSON.number(NSNumber(value: val)) 61 | case .string(let val): obj["id"] = JSON.string(val) 62 | } 63 | 64 | switch result { 65 | case .success(let result): 66 | obj["result"] = result.encode() 67 | case .error(let error): 68 | obj["error"] = JSON.object([ 69 | "code" : JSON.number(NSNumber(value: error.code)), 70 | "message" : JSON.string(error.message) 71 | ]) 72 | } 73 | 74 | json = JSON.object(obj) 75 | } 76 | 77 | public func data(_ headers: [String : String] = [ : ]) -> Data { 78 | var mutableHeader = headers 79 | 80 | guard let jsonData = try? JSONSerialization.data(withJSONObject: json.JSONObject()) else { 81 | return pattern 82 | } 83 | 84 | if !jsonData.isEmpty { 85 | mutableHeader["Content-Length"] = String(jsonData.count) 86 | } 87 | 88 | var headerData = mutableHeader.map({ "\($0): \($1)" }) 89 | .joined(separator: headerSeparator) 90 | .appending(headerTerminator) 91 | .data(using: .utf8) 92 | 93 | headerData?.append(jsonData) 94 | 95 | let response = headerData ?? pattern 96 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 97 | if let msg = String(bytes: response, encoding: .utf8) { 98 | os_log("%{public}@", log: log, type: .default, msg) 99 | } 100 | #endif 101 | return response 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Sources/BaseProtocol/Types/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/23/16. 6 | // 7 | // 8 | 9 | import Ogra 10 | 11 | public enum Result { 12 | case success(Ogra.Encodable) 13 | case error(ServerError) 14 | } 15 | -------------------------------------------------------------------------------- /Sources/LanguageServer/Functions/handle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // handle.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import BaseProtocol 11 | import Foundation 12 | import LanguageServerProtocol 13 | 14 | var workspace: Server! 15 | var exitCode: Int32 = 1 16 | 17 | func handle(_ request: Request) -> Response { 18 | do { 19 | switch request.method { 20 | case "initialize": 21 | let parameters: InitializeParams = try request.parse() 22 | workspace = Server(parameters) 23 | let response = Response(to: request, is: InitializeResult(workspace.capabilities)) 24 | return response 25 | case "workspace/didChangeWatchedFiles": 26 | let parameters: DidChangeWatchedFilesParams = try request.parse() 27 | workspace.receive(notification: parameters) 28 | return Response(to: request, is: JSON.null) 29 | case "textDocument/didChange": 30 | let document: DidChangeTextDocumentParams = try request.parse() 31 | try workspace.client(modified: document) 32 | return Response(to: request, is: JSON.null) 33 | case "textDocument/didClose": 34 | let document: DidCloseTextDocumentParams = try request.parse() 35 | workspace.client(closed: document) 36 | return Response(to: request, is: JSON.null) 37 | case "textDocument/didOpen": 38 | let document: DidOpenTextDocumentParams = try request.parse() 39 | workspace.client(opened: document) 40 | return Response(to: request, is: JSON.null) 41 | // case "textDocument/didSave": 42 | // fatalError("\(request.method) is not implemented yet.") 43 | case "textDocument/completion": 44 | let parameters: TextDocumentPositionParams = try request.parse() 45 | let items = try workspace.complete(forText: parameters) 46 | let response = Response(to: request, is: items) 47 | return response 48 | case "textDocument/definition": 49 | let parameters: TextDocumentPositionParams = try request.parse() 50 | let location = try workspace.findDeclaration(forText: parameters) 51 | let response = Response(to: request, is: location) 52 | return response 53 | case "textDocument/hover": 54 | let parameters: TextDocumentPositionParams = try request.parse() 55 | let hover = try workspace.cursor(forText: parameters) 56 | let response = Response(to: request, is: hover) 57 | return response 58 | // case "textDocument/documentSymbol": 59 | // fatalError("\(request.method) is not implemented yet.") 60 | // case "textDocument/references": 61 | // fatalError("\(request.method) is not implemented yet.") 62 | // case "workspace/symbol": 63 | // fatalError("\(request.method) is not implemented yet.") 64 | case "shutdown": 65 | workspace = nil 66 | // The server should exit with `success` code 0 if the shutdown request has been received 67 | // before; otherwise with `error` code 1. 68 | exitCode = 0 69 | return Response(to: request, is: JSON.null) 70 | case "exit": 71 | exit(exitCode) 72 | default: 73 | throw PredefinedError.methodNotFound 74 | } 75 | } catch let error as ServerError { 76 | return Response(to: request, is: error) 77 | } catch { 78 | return Response(to: request, is: PredefinedError.invalidParams) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/LanguageServer/main.swift: -------------------------------------------------------------------------------- 1 | import BaseProtocol 2 | import Dispatch 3 | import Foundation 4 | import LanguageServerProtocol 5 | 6 | private let header: [String : String] = [ 7 | "Content-Type": "application/vscode-jsonrpc; charset=utf8" 8 | ] 9 | 10 | let readQueue = DispatchQueue(label: "me.lovelett.language-server.dispatchio", qos: .utility, attributes: [], autoreleaseFrequency: .workItem, target: nil) 11 | var requests = RequestBuffer() 12 | 13 | let channel = DispatchIO(type: .stream, fileDescriptor: FileHandle.standardInput.fileDescriptor, queue: readQueue) { (error) in 14 | // If the error parameter contains a non zero value, control was relinquished because there was an error creating 15 | // the channel; otherwise, this value should be 0. 16 | // See: https://developer.apple.com/documentation/dispatch/dispatchio/1388976-init 17 | exit(error) 18 | } 19 | 20 | // The desire is to trigger the handler block as soon as there is any data in the channel. 21 | channel.setLimit(lowWater: 1) 22 | 23 | channel.read(offset: 0, length: Int.max, queue: readQueue) { (done, dispatchData, error) in 24 | switch (done, dispatchData, error) { 25 | case (true, _, 1...): 26 | // If an unrecoverable error occurs on the channel’s file descriptor, the `done` parameter is set to `true` and 27 | // an appropriate error value is reported in the handler’s error parameter. 28 | fatalError("An unrecoverable error occurred on stdin. \(error)") 29 | case (true, let data?, 0) where data.isEmpty: 30 | // If the handler is submitted with the `done` parameter set to `true`, an empty `data` object, and an `error` 31 | // code of `0`, it means that the channel reached the end of the file. 32 | channel.close() 33 | case (_, let data?, 0) where !data.isEmpty: 34 | requests.append(data) 35 | for requestBuffer in requests { 36 | do { 37 | let request = try Request(requestBuffer) 38 | let response = handle(request) 39 | /// If the request id is null then it is a notification and not a request 40 | switch request { 41 | case .request(_, _, _): 42 | let toSend = response.data(header) 43 | // TODO: Writing to stdout should really be done on the main queue 44 | FileHandle.standardOutput.write(toSend) 45 | default: () 46 | } 47 | } catch let error as PredefinedError { 48 | fatalError(error.description) 49 | } catch { 50 | fatalError("TODO: Better error handeling. \(error)") 51 | } 52 | } 53 | if (done) { 54 | // If the `done` parameter is set to `true`, it means the read operation is complete and the handler will 55 | // not be submitted again. 56 | channel.close() 57 | } 58 | default: 59 | fatalError("This is an unexpected case.") 60 | } 61 | } 62 | 63 | // Launch the task 64 | RunLoop.main.run() 65 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Classes/ToolWorkspaceDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolWorkspaceDelegate.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 4/16/17. 6 | // 7 | // 8 | 9 | import Basic 10 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 11 | import os.log 12 | #endif 13 | import PackageGraph 14 | import Workspace 15 | 16 | class ToolWorkspaceDelegate: WorkspaceDelegate { 17 | func packageGraphWillLoad(currentGraph: PackageGraph, dependencies: AnySequence, missingURLs: Set) { 18 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 19 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 20 | os_log("The workspace is about to load the complete package graph.", log: log, type: .default) 21 | #endif 22 | } 23 | 24 | func fetchingWillBegin(repository: String) { 25 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 26 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 27 | os_log("The workspace has started fetching %{public}@", log: log, type: .default, repository) 28 | #endif 29 | } 30 | 31 | func fetchingDidFinish(repository: String, diagnostic: Diagnostic?) { 32 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 33 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 34 | os_log("The workspace has finished fetching %{public}@", log: log, type: .default, repository) 35 | #endif 36 | } 37 | 38 | func repositoryWillUpdate(_ repository: String) { 39 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 40 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 41 | os_log("The workspace has started updating %{public}@", log: log, type: .default, repository) 42 | #endif 43 | } 44 | 45 | func repositoryDidUpdate(_ repository: String) { 46 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 47 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 48 | os_log("The workspace has finished updating %{public}@", log: log, type: .default, repository) 49 | #endif 50 | } 51 | 52 | func cloning(repository: String) { 53 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 54 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 55 | os_log("The workspace has started cloning %{public}@", log: log, type: .default, repository) 56 | #endif 57 | } 58 | 59 | func checkingOut(repository: String, atReference: String, to path: AbsolutePath) { 60 | // FIXME: This is temporary output similar to old one, we will need to figure 61 | // out better reporting text. 62 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 63 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 64 | os_log("The workspace is checking out %{public}@", log: log, type: .default, repository) 65 | #endif 66 | } 67 | 68 | func removing(repository: String) { 69 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 70 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 71 | os_log("The workspace is removing %{public}@ because it is no longer needed.", log: log, type: .default, repository) 72 | #endif 73 | } 74 | 75 | func managedDependenciesDidUpdate(_ dependencies: AnySequence) { 76 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 77 | let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "ToolWorkspaceDelegate") 78 | os_log("Called when the managed dependencies are updated.", log: log, type: .default) 79 | #endif 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Extenstions/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary.swift 3 | // VHS 4 | // 5 | // Created by Ryan Lovelett on 7/13/16. 6 | // Copyright © 2016 Ryan Lovelett. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | /// This extension may eventually become part of the stdlib. It has been proposed as SE-100. 13 | /// 14 | /// Creates a new dictionary using the key/value pairs in the given sequence. 15 | /// 16 | /// - Parameter sequence: A sequence of `(Key, Value)` tuples, where the type `Key` conforms to 17 | /// the `Hashable` protocol. 18 | /// - Returns: A new dictionary initialized with the elements of `sequence`. If `Key` is 19 | /// duplicated then the last value encountered will be the value in the dictionary. 20 | /// 21 | /// - Remark: This extension was initially based on the code provided for Swift-Evolution #100 22 | /// (SE-100). However, that initializer is failable. Since we do not care about duplicates or 23 | /// failures on duplicates that part of the `for-in` loop has been removed. This increases our 24 | /// code-coverage amount without having to add and maintain a test. 25 | /// 26 | /// - SeeAlso: [SE-100: Add sequence-based initializers and merge methods to Dictionary](https://github.com/apple/swift-evolution/blob/8f53b7f467d4ebee6891577311ca70715b4c9834/proposals/0100-add-sequence-based-init-and-merge-to-dictionary.md) 27 | // swiftlint:disable:previous line_length 28 | init(_ sequence: S) where S.Iterator.Element == Element { 29 | self = Dictionary(minimumCapacity: sequence.underestimatedCount) 30 | for (key, value) in sequence { 31 | let _ = self.updateValue(value, forKey: key) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Extenstions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/11/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | /// Create a new `String` by adding the prefix, if defined, to the beginning of the `String`. 14 | /// 15 | /// Example: 16 | /// ======== 17 | /// 18 | /// ``` 19 | /// "base".prepend(Optional.none, separator: "!!") // Returns: "base" 20 | /// "base".prepend(Optional.some("prefix"), separator: "!!") // Returns "prefix!!base" 21 | /// ``` 22 | /// 23 | /// - Parameters: 24 | /// - prefix: If this is `.some(String)` prepend it and the `separator` to the begining. 25 | /// - separator: The value to go between the `prefix` and `base`. 26 | /// - Returns: A new `String` with the `prefix` and `separator` possibly prepended. 27 | func prepend(_ prefix: String?, separator: String) -> String { 28 | guard let p = prefix else { return self } 29 | return "\(p)\(separator)\(self)" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Extenstions/URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/23/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | 12 | extension URL { 13 | 14 | /// Returns `true` if the URL is not a directory on the filesystem. 15 | var isFile: Bool { 16 | guard let resourceMap = try? self.resourceValues(forKeys: [.isDirectoryKey]), let isDirectory = resourceMap.isDirectory else { 17 | return false 18 | } 19 | return !isDirectory 20 | } 21 | 22 | /// Test if a `URL` is a child node of a parent. 23 | /// 24 | /// - Parameter child: The URL to test. 25 | /// - Returns: True if the argument is a child of this instance. 26 | func isParent(of child: URL) -> Bool { 27 | return child.path.hasPrefix(self.path) 28 | } 29 | 30 | } 31 | 32 | extension URL : TextDocumentIdentifier { 33 | 34 | public var uri: URL { 35 | return self 36 | } 37 | 38 | } 39 | 40 | extension URL : Argo.Decodable { 41 | 42 | /// Convert a `JSON.string` case into a `URL`. 43 | /// 44 | /// This decoder has intelligence about dealing with directories and `file://` scheme prefixed strings. 45 | /// 46 | /// - Parameter json: A `JSON.string` containing a valid `URL`. 47 | /// - Returns: The decoded `URL`. 48 | public static func decode(_ json: JSON) -> Decoded { 49 | switch json { 50 | case .string(let uri): 51 | let isDirectory = NSString(string: uri).pathExtension.isEmpty 52 | // Bar is here to handle a URI prefixed with a `file://` scheme 53 | // e.g., file:///Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift 54 | // and 55 | // /Users/ryan/Source/langserver-swift/Fixtures/ValidLayouts/Simple/Sources/main.swift 56 | let bar = URLComponents(string: uri)?.path ?? uri 57 | return pure(URL(fileURLWithPath: bar, isDirectory: isDirectory)) 58 | default: 59 | return .typeMismatch(expected: "String", actual: json) 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Functions/CommonRoot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonRoot.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/24/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Extract the Swift module root directory. 12 | /// 13 | /// The module root directory is the longest common path between two sources in the module. Or 14 | /// if there is only 1 source then its just the last directory. 15 | /// 16 | /// ``` 17 | /// let urls = [ 18 | /// URL(fileURLWithPath: "/module/Sources/dir1/dir1.A/foo.swift", isDirectory: false), 19 | /// URL(fileURLWithPath: "/module/Sources/dir1/bar.swift", isDirectory: false) 20 | /// ] 21 | /// commonRoot(urls) // Returns /module/Sources/dir1/ 22 | /// ``` 23 | /// 24 | /// - Precondition: The collection cannot be empty. 25 | /// 26 | /// - Parameter sources: A collection of fully qualified paths to Swift sources in a module. 27 | /// - Returns: The root directory of the collection. 28 | func commonRoot(_ sources: C) -> URL 29 | where C.Iterator.Element == URL 30 | { 31 | precondition(!sources.isEmpty, "A module must have at least 1 source.") 32 | if sources.count == 1, let single = sources.first { 33 | return single.deletingLastPathComponent() 34 | } else { 35 | let first = sources[sources.startIndex] 36 | let second = sources[sources.index(after: sources.startIndex)] 37 | return URL(fileURLWithPath: first.path.commonPrefix(with: second.path), isDirectory: true) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Functions/Convert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Convert.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/10/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | 12 | fileprivate let regex = try! NSRegularExpression(pattern: "<#T##([^#]+)#(?:#[^#]+#)?>", options: []) 13 | 14 | /// Convert a SourceKit (read: Xcode) style snippet to a 15 | /// [TextMate snippet syntax](https://manual.macromates.com/en/snippets). 16 | /// 17 | /// Example: 18 | /// ======== 19 | /// 20 | /// ``` 21 | /// let str = "fatalError(<#T##message: String##String#>)" 22 | /// convert(str).value! // "fatalError(${1:message: String})" 23 | /// ``` 24 | /// 25 | /// - Parameter sourceKit: A `String` possibly containing an SourceKit style snippet. 26 | /// - Returns: A `String` where any SourceKit style snippets have been converted to TextMate snippets. 27 | func convert(_ sourceKit: String) -> Decoded { 28 | var result = "" 29 | var lastRange = sourceKit.startIndex.. Void in 33 | guard 34 | let matchRange = (x?.range(at: 0)).flatMap({ Range($0, in: sourceKit) }), 35 | let group = (x?.range(at: 1)).flatMap({ Range($0, in: sourceKit) }).flatMap({ String(sourceKit[$0]) }) 36 | else { 37 | return 38 | } 39 | cursorIndex += 1 40 | result += sourceKit[lastRange.upperBound..` and a 45 | /// completion item with an `insertText` of `console` is provided it will only insert `sole`. Therefore it is 46 | /// recommended to use `textEdit` instead since it avoids additional client side interpretation. 47 | /// 48 | /// - Warning: This property is deprecated. Use `textEdit` instead. 49 | var insertText: String? 50 | 51 | /// The format of the insert text. The format applies to both the `insertText` property and the `newText` property 52 | /// of a provided `textEdit`. 53 | var insertTextFormat: InsertTextFormat? = .snippet 54 | 55 | /// An edit which is applied to a document when selecting this completion. When an edit is 56 | /// provided the value of insertText is ignored. 57 | var textEdit: TextEdit? 58 | 59 | /// An optional array of additional text edits that are applied when selecting this completion. 60 | /// Edits must not overlap with the main edit nor with themselves. 61 | var additionalTextEdits: [TextEdit]? 62 | 63 | /// An optional command that is executed *after* inserting this completion. 64 | /// 65 | /// - Note: That additional modifications to the current document should be described with the 66 | /// `additionalTextEdits`-property. 67 | var command: Command? 68 | 69 | /// An data entry field that is preserved on a completion item between a completion and a 70 | /// completion resolve request. 71 | var data: Any? 72 | 73 | } 74 | 75 | extension CompletionItem : Ogra.Encodable { 76 | 77 | public func encode() -> JSON { 78 | var obj: [String : JSON] = [ 79 | "label" : JSON.string(label) 80 | ] 81 | 82 | if let kind = self.kind { 83 | obj["kind"] = JSON.number(NSNumber(value: kind.rawValue)) 84 | } 85 | 86 | if let detail = self.detail { 87 | obj["detail"] = JSON.string(detail) 88 | } 89 | 90 | if let documentation = self.documentation { 91 | obj["documentation"] = JSON.string(documentation) 92 | } 93 | 94 | if let sortText = self.sortText { 95 | obj["sortText"] = JSON.string(sortText) 96 | } 97 | 98 | if let filterText = self.filterText { 99 | obj["filterText"] = JSON.string(filterText) 100 | } 101 | 102 | if let insertText = self.insertText { 103 | obj["insertText"] = JSON.string(insertText) 104 | } 105 | 106 | if let insertTextFormat = self.insertTextFormat { 107 | obj["insertTextFormat"] = JSON.number(NSNumber(value: insertTextFormat.rawValue)) 108 | } 109 | 110 | if let textEdit = self.textEdit { 111 | obj["textEdit"] = textEdit.encode() 112 | } 113 | // 114 | // if let additionalTextEdits = self.additionalTextEdits { 115 | // obj["additionalTextEdits"] = additionalTextEdits.flatMap({ $0.message }) 116 | // } 117 | // 118 | // if let command = self.command { 119 | // obj["command"] = command.encode() 120 | // } 121 | // 122 | // if let data = self.data { 123 | // obj["data"] = data 124 | // } 125 | 126 | return JSON.object(obj) 127 | } 128 | 129 | } 130 | 131 | extension CompletionItem : Argo.Decodable { 132 | 133 | fileprivate init(description: String, k: CompletionItemKind?, sourcetext: String, type: String?, brief: String?, context: String, bytesToErase: Int, associatedUSRs: String?, name: String, module: String?, notRecommended: Bool?) { 134 | label = description 135 | detail = description 136 | kind = k 137 | documentation = brief 138 | sortText = nil 139 | filterText = nil 140 | insertText = sourcetext 141 | textEdit = nil 142 | additionalTextEdits = nil 143 | command = nil 144 | data = nil 145 | 146 | let base = type?.prepend(module, separator: ".") 147 | switch k! { 148 | // case .Text: 149 | // case .Method: 150 | case .Function: 151 | detail = base?.prepend(description, separator: " -> ") 152 | case .Constructor: 153 | detail = description.prepend(base, separator: " ") 154 | // case .Field: 155 | // case .Variable: 156 | // case .Class: 157 | // case .Interface: 158 | // case .Module: 159 | // case .Property: 160 | // case .Unit: 161 | // case .Value: 162 | // case .Enum: 163 | // case .Keyword: 164 | // case .Snippet: 165 | // case .Color: 166 | // case .File: 167 | // case .Reference: 168 | default: 169 | detail = description 170 | } 171 | } 172 | 173 | /// completion-result ::= 174 | /// { 175 | /// : (string) // Text to be displayed in code-completion window. 176 | /// : (UID) // UID for the declaration kind (function, class, etc.). 177 | /// : (string) // Text to be inserted in source. 178 | /// : (string) // Text describing the type of the result. 179 | /// : (string) // Brief documentation comment attached to the entity. 180 | /// : (UID) // Semantic context of the code completion result. 181 | /// : (int64) // Number of bytes to the left of the cursor that should be erased before inserting this completion result. 182 | /// } 183 | /// 184 | /// ``` 185 | /// $ cat Fixtures/JSON/complete.json | jq '.[] | map(keys) | flatten | unique' 186 | /// [ 187 | /// "key.associated_usrs", 188 | /// "key.context", 189 | /// "key.description", 190 | /// "key.doc.brief", 191 | /// "key.kind", 192 | /// "key.modulename", 193 | /// "key.name", 194 | /// "key.not_recommended", 195 | /// "key.num_bytes_to_erase", 196 | /// "key.sourcetext", 197 | /// "key.typename" 198 | /// ] 199 | /// ``` 200 | /// 201 | /// 202 | /// - Remark: Need to document: 203 | /// "key.name" : "#colorLiteral(red:green:blue:alpha:)" 204 | public static func decode(_ json: JSON) -> Decoded { 205 | let description: Decoded = json["key.description"] 206 | let kind: Decoded = json[optional: "key.kind"] 207 | let sourcetext: Decoded = json["key.sourcetext"].flatMap(convert) 208 | let type: Decoded = json[optional: "key.typename"] 209 | let brief: Decoded = json[optional: "key.doc.brief"] 210 | let context: Decoded = json["key.context"] 211 | let bytesToErase: Decoded = json["key.num_bytes_to_erase"] 212 | 213 | let associatedUSRs: Decoded = json[optional: "key.associated_usrs"] 214 | let name: Decoded = json["key.name"] 215 | let module: Decoded = json[optional: "key.modulename"] 216 | let notRecommended: Decoded = json[optional: "key.not_recommended"] 217 | 218 | let x = curry(CompletionItem.init) 219 | <^> description 220 | <*> kind 221 | <*> sourcetext 222 | <*> type 223 | <*> brief 224 | <*> context 225 | <*> bytesToErase 226 | <*> associatedUSRs 227 | <*> name 228 | <*> module 229 | <*> notRecommended 230 | 231 | return x 232 | } 233 | 234 | } 235 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/CompletionItemKind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionItemKind.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/2/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Runes 11 | 12 | /// The kind of a completion entry. 13 | public enum CompletionItemKind : Int { 14 | case Text = 1 15 | case Method = 2 16 | case Function = 3 17 | case Constructor = 4 18 | case Field = 5 19 | case Variable = 6 20 | case Class = 7 21 | case Interface = 8 22 | case Module = 9 23 | case Property = 10 24 | case Unit = 11 25 | case Value = 12 26 | case Enum = 13 27 | case Keyword = 14 28 | case Snippet = 15 29 | case Color = 16 30 | case File = 17 31 | case Reference = 18 32 | 33 | init(_ str: String) { 34 | switch str { 35 | case "source.lang.swift.decl.function.free", 36 | "source.lang.swift.decl.function.method.instance": 37 | self = .Function 38 | case "source.lang.swift.decl.function.constructor": 39 | self = .Constructor 40 | case "source.lang.swift.decl.var.global", 41 | "source.lang.swift.decl.var.instance": 42 | self = .Variable 43 | case "source.lang.swift.decl.class": 44 | self = .Class 45 | case "source.lang.swift.decl.protocol": 46 | self = .Interface 47 | case "source.lang.swift.decl.struct": 48 | self = .Value //Maybe? 49 | case "source.lang.swift.decl.enum": 50 | self = .Enum 51 | case "source.lang.swift.keyword", 52 | "source.lang.swift.decl.function.operator.infix": 53 | self = .Keyword 54 | case "source.lang.swift.literal.color": 55 | self = .Color 56 | case "source.lang.swift.literal.image": 57 | self = .File 58 | case "source.lang.swift.decl.typealias": 59 | self = .Reference 60 | default: 61 | self = .Text 62 | } 63 | } 64 | } 65 | 66 | extension CompletionItemKind : Argo.Decodable { 67 | 68 | public static func decode(_ json: JSON) -> Decoded { 69 | switch json { 70 | case .string(let kind): 71 | return pure(CompletionItemKind(kind)) 72 | default: 73 | return .typeMismatch(expected: "String", actual: json) 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/CompletionOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionOptions.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/26/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | /// Completion options 13 | public struct CompletionOptions { 14 | 15 | /// The server provides support to resolve additional information for a completion item. 16 | let resolveProvider: Bool? 17 | 18 | /// The characters that trigger completion automatically. 19 | let triggerCharacters: [String]? 20 | 21 | } 22 | 23 | extension CompletionOptions : Ogra.Encodable { 24 | 25 | public func encode() -> JSON { 26 | var obj: [String : Any] = [ : ] 27 | 28 | if let resolveProvider = self.resolveProvider { 29 | obj["resolveProvider"] = resolveProvider 30 | } 31 | 32 | if let triggerCharacters = self.triggerCharacters { 33 | obj["triggerCharacters"] = triggerCharacters 34 | } 35 | 36 | return JSON(obj) 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/Cursor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cursor.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Runes 13 | 14 | struct Cursor { 15 | 16 | enum DefinitionLocation { 17 | case local(filepath: URL, offset: UInt64, length: UInt64) 18 | case system(moduleName: String, groupName: String, isSystem: Bool) 19 | } 20 | 21 | /// UID for the declaration or reference kind (function, class, etc.). 22 | let kind: String 23 | 24 | /// Displayed name for the token. 25 | let name: String 26 | 27 | /// USR string for the token. 28 | let usr: String 29 | 30 | /// Text describing the type of the result. 31 | let typename: String 32 | 33 | /// XML representing how the token was declared. 34 | let annotatedDeclaration: String 35 | 36 | /// XML representing the token. 37 | let fullyAnnotatedDeclaration: String 38 | 39 | /// XML representing the token and its documentation. 40 | let documentationAsXML: String? 41 | 42 | /// USR string for the type. 43 | let typeusr: String 44 | 45 | let defined: DefinitionLocation 46 | 47 | } 48 | 49 | extension Cursor : Argo.Decodable { 50 | 51 | static func decode(_ json: JSON) -> Decoded { 52 | return curry(Cursor.init) 53 | <^> json["key.kind"] 54 | <*> json["key.name"] 55 | <*> json["key.usr"] 56 | <*> json["key.typename"] 57 | <*> json["key.annotated_decl"] 58 | <*> json["key.fully_annotated_decl"] 59 | <*> json[optional: "key.doc.full_as_xml"] 60 | <*> json["key.typeusr"] 61 | <*> Cursor.DefinitionLocation.decode(json) 62 | } 63 | 64 | } 65 | 66 | extension Cursor.DefinitionLocation : Argo.Decodable { 67 | 68 | static func decode(_ json: JSON) -> Decoded { 69 | let filepath: String? = json["key.filepath"].value 70 | let offset: UInt64? = json["key.offset"].value 71 | let length: UInt64? = json["key.length"].value 72 | let isSystem: Bool = json["key.is_system"].value ?? false 73 | let moduleName: String? = json["key.modulename"].value 74 | let groupName: String? = json["key.groupname"].value 75 | 76 | switch (filepath, offset, length, moduleName, groupName) { 77 | case let (f?, o?, l?, _, _): 78 | return pure(Cursor.DefinitionLocation.local(filepath: URL(fileURLWithPath: f), offset: o, length: l)) 79 | case let (_, _, _, mn?, gn?): 80 | return pure(Cursor.DefinitionLocation.system(moduleName: mn, groupName: gn, isSystem: isSystem)) 81 | default: 82 | return .customError("Could not determine if this is a system or local module.") 83 | } 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/DidChangeTextDocumentParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DidChangeTextDocumentParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/7/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | public struct DidChangeTextDocumentParams { 14 | 15 | /// The document that did change. The version number points to the version after all provided 16 | /// content changes have been applied. 17 | let textDocument: VersionedTextDocumentIdentifier 18 | 19 | /// The actual content changes. 20 | let contentChanges: [TextDocumentContentChangeEvent] 21 | 22 | } 23 | 24 | extension DidChangeTextDocumentParams : Argo.Decodable { 25 | 26 | public static func decode(_ json: JSON) -> Decoded { 27 | let textDocument: Decoded = json["textDocument"] 28 | let contentChanges: Decoded<[TextDocumentContentChangeEvent]> = json["contentChanges"] 29 | return curry(DidChangeTextDocumentParams.init) <^> textDocument <*> contentChanges 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/DidChangeWatchedFilesParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DidChangeWatchedFilesParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/17/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | public struct DidChangeWatchedFilesParams { 14 | 15 | /// The actual file events. 16 | let changes: [FileEvent] 17 | 18 | } 19 | 20 | extension DidChangeWatchedFilesParams : Argo.Decodable { 21 | 22 | public static func decode(_ json: JSON) -> Decoded { 23 | return curry(DidChangeWatchedFilesParams.init(changes:)) <^> json["changes"] 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/DidCloseTextDocumentParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DidCloseTextDocumentParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/5/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | /// The document close notification is sent from the client to the server when the document got 14 | /// closed in the client. The document's truth now exists where the document's uri points to (e.g. 15 | /// if the document's uri is a file uri the truth now exists on disk). 16 | public struct DidCloseTextDocumentParams { 17 | 18 | /// The document that was closed. 19 | let textDocument: TextDocumentIdentifier 20 | 21 | } 22 | 23 | extension DidCloseTextDocumentParams : Argo.Decodable { 24 | 25 | public static func decode(_ json: JSON) -> Decoded { 26 | let textDocument: Decoded = json["textDocument"] 27 | return curry(DidCloseTextDocumentParams.init) <^> textDocument 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/DidOpenTextDocumentParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DidOpenTextDocumentParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/4/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | /// The document open notification is sent from the client to the server to signal newly opened text 14 | /// documents. The document's truth is now managed by the client and the server must not try to read 15 | /// the document's truth using the document's uri. 16 | public struct DidOpenTextDocumentParams { 17 | 18 | /// The document that was opened. 19 | let textDocument: TextDocument 20 | 21 | } 22 | 23 | extension DidOpenTextDocumentParams : Argo.Decodable { 24 | 25 | public static func decode(_ json: JSON) -> Decoded { 26 | let textDocument: Decoded = json["textDocument"] 27 | return curry(DidOpenTextDocumentParams.init) <^> textDocument 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/FileChangeType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileChangeType.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/17/16. 6 | // 7 | // 8 | 9 | import Argo 10 | 11 | /// The file event type 12 | enum FileChangeType: Int { 13 | 14 | /// The file got created. 15 | case Created = 1 16 | 17 | /// The file got changed. 18 | case Changed = 2 19 | 20 | /// The file got deleted. 21 | case Deleted = 3 22 | 23 | } 24 | 25 | extension FileChangeType : Argo.Decodable { } 26 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/FileEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileEvent.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/17/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Runes 13 | 14 | /// An event describing a file change. 15 | struct FileEvent { 16 | 17 | /// The file's URI 18 | let uri: URL 19 | 20 | /// The change type. 21 | let type: FileChangeType 22 | 23 | } 24 | 25 | extension FileEvent : Argo.Decodable { 26 | 27 | static func decode(_ json: JSON) -> Decoded { 28 | return curry(FileEvent.init) 29 | <^> json["uri"] 30 | <*> json["type"] 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/Hover.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Hover.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/11/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | /// The marked string is rendered: 13 | /// - as markdown if it is represented as a string 14 | /// - as code block of the given langauge if it is represented as a pair of a language and a value 15 | /// 16 | /// The pair of a language and a value is an equivalent to markdown: 17 | /// ```${language} 18 | /// ${value} 19 | /// ``` 20 | /// 21 | /// `type MarkedString = string | { language: string; value: string };` 22 | public struct MarkedString { 23 | let language: String 24 | let value: String 25 | } 26 | 27 | extension MarkedString : Ogra.Encodable { 28 | 29 | public func encode() -> JSON { 30 | return JSON.object([ 31 | "language" : language.encode(), 32 | "value" : value.encode(), 33 | ]) 34 | } 35 | 36 | } 37 | 38 | /// The result of a hover request. 39 | public struct Hover { 40 | 41 | /// The hover's content 42 | let contents: [MarkedString] 43 | 44 | /// An optional range is a range inside a text document that is used to visualize a hover, 45 | /// e.g. by changing the background color. 46 | let range: TextDocumentRange? 47 | 48 | } 49 | 50 | extension Hover : Ogra.Encodable { 51 | 52 | public func encode() -> JSON { 53 | var obj = [ 54 | "contents" : contents.encode() 55 | ] 56 | 57 | if let range = range { 58 | obj["range"] = range.encode() 59 | } 60 | 61 | return JSON.object(obj) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/InitializeParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InitializeParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | /// The initialize request is sent as the first request from the client to the server. 14 | public struct InitializeParams { 15 | 16 | /// The process Id of the parent process that started the server. Is null if the process has not 17 | /// been started by another process. 18 | /// If the parent process is not alive then the server should exit (see exit notification) its 19 | /// process. 20 | let processId: Int? 21 | 22 | /// The rootPath of the workspace. Is null if no folder is open. 23 | let rootPath: String? 24 | 25 | /// User provided initialization options. 26 | let initializationOptions: Any? 27 | 28 | } 29 | 30 | extension InitializeParams : Argo.Decodable { 31 | 32 | public static func decode(_ json: JSON) -> Decoded { 33 | let p: Decoded = json[optional: "processId"] 34 | let r: Decoded = json[optional: "rootPath"] 35 | return curry(InitializeParams.init) <^> p <*> r <*> pure(.none) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/InitializeResult.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InitializeResult.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | /// The response to the 'initialize' request. 13 | public struct InitializeResult { 14 | 15 | /// The capabilities the language server provides. 16 | let capabilities: ServerCapabilities 17 | 18 | public init(_ c: ServerCapabilities) { 19 | capabilities = c 20 | } 21 | 22 | } 23 | 24 | extension InitializeResult : Ogra.Encodable { 25 | 26 | public func encode() -> JSON { 27 | return JSON.object([ 28 | "capabilities": capabilities.encode() 29 | ]) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/InsertTextFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InsertTextFormat.swift 3 | // LanguageServerProtocol 4 | // 5 | // Created by Ryan Lovelett on 5/28/18. 6 | // 7 | 8 | /// Defines whether the insert text in a completion item should be interpreted as plain text or a snippet. 9 | public enum InsertTextFormat: Int { 10 | /// The primary text to be inserted is treated as a plain string. 11 | case plainText = 1 12 | 13 | /// The primary text to be inserted is treated as a snippet. 14 | /// 15 | /// A snippet can define tab stops and placeholders with `$1`, `$2` and `${3:foo}`. `$0` defines the final tab stop, 16 | /// it defaults to the end of the snippet. Placeholders with equal identifiers are linked, that is typing in one 17 | /// will update others too. 18 | case snippet = 2 19 | } 20 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/LineCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineCollection.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | fileprivate let lf: UInt8 = 0x0A 12 | 13 | fileprivate func buildLineRangeCollection(from data: Data) -> [Range] { 14 | var array: [Range] = [] 15 | var lower: Data.Index = data.startIndex 16 | var last: Range = Range(data.startIndex..(lower...upper) 19 | array.append(last) 20 | // The next lowest is greater than the current index 21 | lower = (upper + 1) 22 | } 23 | // File does not have a trailing line-feed 24 | if last.upperBound != data.endIndex { 25 | let end = Range(last.upperBound..] 36 | 37 | init(for file: URL) throws { 38 | data = try Data(contentsOf: file, options: .mappedIfSafe) 39 | lines = buildLineRangeCollection(from: data) 40 | } 41 | 42 | init?(for string: String) { 43 | guard let d = string.data(using: .utf8) else { 44 | return nil 45 | } 46 | data = d 47 | lines = buildLineRangeCollection(from: data) 48 | } 49 | 50 | func byteOffset(at: Position) throws -> Int { 51 | guard at.line < lines.count else { throw WorkspaceError.positionNotFound } 52 | let lineRange = lines[at.line] 53 | let offset = lineRange.lowerBound.advanced(by: at.character) 54 | guard offset < lineRange.upperBound else { throw WorkspaceError.positionNotFound } 55 | return offset 56 | } 57 | 58 | func position(for offset: Int) throws -> Position { 59 | guard let lineIndex = lines.index(where: { $0.contains(offset) }) else { throw WorkspaceError.positionNotFound } 60 | let lineRange = lines[lineIndex] 61 | let x = offset - lineRange.lowerBound 62 | let position = Position(line: lineIndex, character: x) 63 | return position 64 | } 65 | 66 | func selection(startAt offset: Int, length: Int) throws -> TextDocumentRange { 67 | let endOffset = Int(offset + length) 68 | let start = try position(for: offset) 69 | let end = try position(for: endOffset) 70 | return TextDocumentRange(start: start, end: end) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 10/25/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | /// Represents a location inside a resource, such as a line inside a text file. 13 | public struct Location { 14 | 15 | let uri: String 16 | 17 | let range: TextDocumentRange 18 | 19 | } 20 | 21 | extension Location : Ogra.Encodable { 22 | 23 | public func encode() -> JSON { 24 | return JSON.object([ 25 | "uri" : JSON.string(uri), 26 | "range" : range.encode() 27 | ]) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/Position.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Position.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/23/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Ogra 13 | import Runes 14 | 15 | /// Position in a text document expressed as zero-based line and character offset. A position is 16 | /// between two characters like an 'insert' cursor in a editor. 17 | public struct Position { 18 | 19 | /// Line position in a document (zero-based). 20 | let line: Int 21 | 22 | /// Character offset on a line in a document (zero-based). 23 | let character: Int 24 | 25 | } 26 | 27 | extension Position : Argo.Decodable { 28 | 29 | public static func decode(_ json: JSON) -> Decoded { 30 | let l: Decoded = json["line"] 31 | let c: Decoded = json["character"] 32 | return curry(Position.init) <^> l <*> c 33 | } 34 | 35 | } 36 | 37 | extension Position : Equatable { 38 | /// Returns a Boolean value indicating whether two values are equal. 39 | /// 40 | /// Equality is the inverse of inequality. For any values `a` and `b`, 41 | /// `a == b` implies that `a != b` is `false`. 42 | /// 43 | /// - Parameters: 44 | /// - lhs: A value to compare. 45 | /// - rhs: Another value to compare. 46 | public static func ==(lhs: Position, rhs: Position) -> Bool { 47 | return lhs.line == rhs.line && lhs.character == rhs.character 48 | } 49 | } 50 | 51 | extension Position : Ogra.Encodable { 52 | 53 | public func encode() -> JSON { 54 | return JSON.object([ 55 | "line" : JSON.number(NSNumber(value: line)), 56 | "character" : JSON.number(NSNumber(value: character)) 57 | ]) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/ServerCapabilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServerCapabilities.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | import Ogra 12 | 13 | /// The capabilities the language server provides. 14 | public struct ServerCapabilities { 15 | 16 | /// Defines how text documents are synced. 17 | let textDocumentSync: TextDocumentSyncKind? 18 | 19 | /// The server provides hover support. 20 | let hoverProvider: Bool? 21 | 22 | /// The server provides completion support. 23 | let completionProvider: CompletionOptions? 24 | 25 | /// The server provides goto definition support. 26 | let definitiionProvider: Bool? 27 | 28 | /// The server provides find references support. 29 | let referencesProvider: Bool? 30 | 31 | /// The server provides document highlight support. 32 | let documentHighlighProvider: Bool? 33 | 34 | /// The server provides document symbol support. 35 | let documentSymbolProvider: Bool? 36 | 37 | /// The server provides workspace symbol support. 38 | let workspaceSymbolProvider: Bool? 39 | 40 | /// The server provides code actions. 41 | let codeActionProvider: Bool? 42 | 43 | /// The server provides document formatting. 44 | let documentFormattingProvider: Bool? 45 | 46 | /// The server provides document range formatting. 47 | let documentRangeFormattingProvider: Bool? 48 | 49 | /// The server provides rename support. 50 | let renameProvider: Bool? 51 | 52 | } 53 | 54 | extension ServerCapabilities : Ogra.Encodable { 55 | 56 | public func encode() -> JSON { 57 | var obj: [String : JSON] = [ : ] 58 | 59 | if let textDocumentSync = self.textDocumentSync { 60 | obj["textDocumentSync"] = JSON.number(NSNumber(value: textDocumentSync.rawValue)) 61 | } 62 | 63 | if let hoverProvider = self.hoverProvider { 64 | obj["hoverProvider"] = JSON.bool(hoverProvider) 65 | } 66 | 67 | if let completionProvider = self.completionProvider { 68 | obj["completionProvider"] = completionProvider.encode() 69 | } 70 | 71 | if let definitiionProvider = self.definitiionProvider { 72 | obj["definitionProvider"] = JSON.bool(definitiionProvider) 73 | } 74 | 75 | if let workspaceSymbolProvider = self.workspaceSymbolProvider { 76 | obj["workspaceSymbolProvider"] = JSON.bool(workspaceSymbolProvider) 77 | } 78 | 79 | return JSON.object(obj) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/SwiftModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftModule.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/18/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import enum Build.TargetDescription 11 | import Curry 12 | import Foundation 13 | import class PackageModel.ResolvedTarget 14 | import Runes 15 | 16 | private extension TargetDescription { 17 | 18 | /// The compiler arguments that will be sent to SourceKit. 19 | /// 20 | /// In the future, this may or may not be added to `libSwiftPM`. 21 | var arguments: [String] { 22 | switch self { 23 | case .clang(let c): 24 | return c.basicArguments() 25 | case .swift(let s): 26 | return s.compileArguments() 27 | } 28 | } 29 | 30 | } 31 | 32 | /// A module, typically defined and managed by [SwiftPM](), that manages the sources and the compiler arguments 33 | /// that should be sent to SourceKit. 34 | struct SwiftModule { 35 | 36 | /// The name of the Swift module. 37 | let name: String 38 | 39 | /// A mapping of the raw text of a source to it's location on the file system. 40 | var sources: [URL : TextDocument] 41 | 42 | /// The raw arguments provided by SwiftPM. 43 | let otherArguments: [String] 44 | 45 | /// Arguments to be sent to SourceKit. 46 | var arguments: [String] { 47 | return sources.keys.map({ $0.path }) + otherArguments 48 | } 49 | 50 | /// Create a false module that is just a collection of source files in a directory. Ideally 51 | /// this should not be used since SwiftPM defined modules are preferred. 52 | /// 53 | /// - Parameter directory: A directory containing a collection of Swift source files. 54 | init(_ directory: URL) { 55 | name = directory.lastPathComponent 56 | let s = WorkspaceSequence(root: directory).lazy 57 | .filter({ $0.isFileURL && $0.isFile }) 58 | .filter({ $0.pathExtension.lowercased() == "swift" }) // Check if file is a Swift source file (e.g., has `.swift` extension) 59 | .compactMap(TextDocument.init) 60 | .map({ (key: $0.uri, value: $0) }) 61 | sources = Dictionary(s) 62 | otherArguments = [] 63 | } 64 | 65 | /// Create a module from the definition provided by SwiftPM. 66 | /// 67 | /// - Parameters: 68 | /// - target: A fully resolved target. All the dependencies for the target are resolved. 69 | /// - description: The description of either a Swift or Clang target. 70 | init(target: ResolvedTarget, description: TargetDescription) { 71 | name = target.name 72 | let dict = target.sources.paths.lazy 73 | .map { URL(fileURLWithPath: $0.asString) } 74 | .flatMap(TextDocument.init) 75 | .map { (key: $0.uri, value: $0) } 76 | sources = Dictionary(dict) 77 | otherArguments = description.arguments 78 | } 79 | 80 | var root: URL { 81 | return commonRoot(sources.keys) 82 | } 83 | 84 | } 85 | 86 | extension SwiftModule : Hashable { 87 | 88 | var hashValue: Int { 89 | return name.hashValue 90 | } 91 | 92 | } 93 | 94 | extension SwiftModule : Equatable { 95 | 96 | /// Returns a Boolean value indicating whether two values are equal. 97 | /// 98 | /// Equality is the inverse of inequality. For any values `a` and `b`, 99 | /// `a == b` implies that `a != b` is `false`. 100 | /// 101 | /// - Parameters: 102 | /// - lhs: A value to compare. 103 | /// - rhs: Another value to compare. 104 | static func ==(lhs: SwiftModule, rhs: SwiftModule) -> Bool { 105 | return lhs.name == rhs.name 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextDocument.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 10/25/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Runes 13 | 14 | /// The representation of a Swift source file. 15 | /// 16 | /// This type is used by both the client and server to transfer a Swift source. 17 | struct TextDocument : TextDocumentItem { 18 | 19 | /// The source file's fully qualified path on the filesystem. 20 | let uri: URL 21 | 22 | /// The text document's language identifier. 23 | let languageId: String 24 | 25 | /// The version number of this document (it will strictly increase after each change, 26 | /// including undo/redo). 27 | let version: Int 28 | 29 | /// The content of the opened text document. 30 | let text: String 31 | 32 | /// A collection of Ranges that denote the different byte position of lines in a document. 33 | let lines: LineCollection 34 | 35 | } 36 | 37 | extension TextDocument { 38 | 39 | /// Attempt to create an instance for a file location. It will fail to initialize if the file cannot be read. 40 | /// 41 | /// - Parameter file: The file, on the local file system, that should be read. 42 | init?(_ file: URL) { 43 | uri = file 44 | languageId = "swift" 45 | version = Int.min 46 | text = "" 47 | guard let lines = try? LineCollection(for: file) else { return nil } 48 | self.lines = lines 49 | } 50 | 51 | /// Create a text document instance from information sent by the client. 52 | /// 53 | /// - Parameters: 54 | /// - uri: The source file's fully qualified path on the filesystem. 55 | /// - languageId: The text document's language identifier, typically `"swift"`. 56 | /// - version: The version number of this document. 57 | /// - text: The content of the opened text document. 58 | fileprivate init(uri: URL, languageId: String, version: Int, text: String) { 59 | self.uri = uri 60 | self.languageId = languageId 61 | self.version = version 62 | self.text = text 63 | // TODO: Force cast 🤢 64 | self.lines = LineCollection(for: text)! 65 | } 66 | 67 | /// Create a new instance from the current instance, whild changing the version and text. 68 | /// 69 | /// - Parameters: 70 | /// - version: The version to set the new text document to. 71 | /// - andText: The content of the new text document. 72 | /// - Returns: A new copy of the current text document with the changed parameters. 73 | func update(version: Int, andText: String) -> TextDocument { 74 | return TextDocument(uri: uri, languageId: languageId, version: version, text: andText) 75 | } 76 | 77 | } 78 | 79 | extension TextDocument : Argo.Decodable { 80 | 81 | /// Create a new text document from JSON sent by the client. 82 | /// 83 | /// - Parameter json: The JSON from the client. 84 | /// - Returns: The result of decoding the JSON. 85 | public static func decode(_ json: JSON) -> Decoded { 86 | let uri: Decoded = json["uri"] 87 | let languageId: Decoded = json["languageId"] 88 | let version: Decoded = json["version"] 89 | let text: Decoded = json["text"] 90 | return curry(TextDocument.init) <^> uri <*> languageId <*> version <*> text 91 | } 92 | 93 | } 94 | 95 | extension TextDocument : Hashable { 96 | 97 | var hashValue: Int { 98 | return uri.hashValue 99 | } 100 | 101 | } 102 | 103 | extension TextDocument : Equatable { 104 | 105 | /// Returns a Boolean value indicating whether two values are equal. 106 | /// 107 | /// Equality is the inverse of inequality. For any values `a` and `b`, 108 | /// `a == b` implies that `a != b` is `false`. 109 | /// 110 | /// - Parameters: 111 | /// - lhs: A value to compare. 112 | /// - rhs: Another value to compare. 113 | static func ==(lhs: TextDocument, rhs: TextDocument) -> Bool { 114 | return lhs.uri == rhs.uri 115 | && lhs.languageId == rhs.languageId 116 | && lhs.version == rhs.version 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextDocumentContentChangeEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextDocumentContentChangeEvent.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/7/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Runes 12 | 13 | /// An event describing a change to a text document. If range and rangeLength are omitted the new 14 | /// text is considered to be the full content of the document. 15 | struct TextDocumentContentChangeEvent { 16 | 17 | /// The range of the document that changed. 18 | let range: TextDocumentRange? 19 | 20 | /// The length of the range that got replaced. 21 | let rangeLength: Int? 22 | 23 | /// The new text of the document. 24 | let text: String 25 | 26 | } 27 | 28 | extension TextDocumentContentChangeEvent : Argo.Decodable { 29 | 30 | static func decode(_ json: JSON) -> Decoded { 31 | let range: Decoded = json[optional: "range"] 32 | let rangeLength: Decoded = json[optional: "rangeLength"] 33 | let text: Decoded = json["text"] 34 | return curry(TextDocumentContentChangeEvent.init) <^> range <*> rangeLength <*> text 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextDocumentPositionParams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextDocumentPositionParams.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Runes 13 | 14 | /// A parameter literal used in requests to pass a text document and a position inside that document. 15 | public struct TextDocumentPositionParams { 16 | 17 | /// The text document. 18 | let textDocument: TextDocumentIdentifier 19 | 20 | /// The position inside the text document. 21 | let position: Position 22 | 23 | } 24 | 25 | extension TextDocumentPositionParams : Argo.Decodable { 26 | 27 | public static func decode(_ json: JSON) -> Decoded { 28 | let td: Decoded = json["textDocument", "uri"] 29 | let p: Decoded = json["position"] 30 | return curry(TextDocumentPositionParams.init) <^> td <*> p 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextDocumentRange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextDocumentRange.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/23/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Ogra 12 | import Runes 13 | 14 | /// A range in a text document expressed as (zero-based) start and end positions. A range is 15 | /// comparable to a selection in an editor. Therefore the end position is exclusive. 16 | struct TextDocumentRange { 17 | 18 | /// The range's start position. 19 | let start: Position 20 | 21 | /// The range's start position. 22 | let end: Position 23 | 24 | } 25 | 26 | extension TextDocumentRange : Argo.Decodable { 27 | 28 | static func decode(_ json: JSON) -> Decoded { 29 | let start: Decoded = json["start"] 30 | let end: Decoded = json["end"] 31 | return curry(TextDocumentRange.init) <^> start <*> end 32 | } 33 | 34 | } 35 | 36 | extension TextDocumentRange : Ogra.Encodable { 37 | 38 | public func encode() -> JSON { 39 | return JSON.object([ 40 | "start" : start.encode(), 41 | "end" : end.encode() 42 | ]) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextDocumentSyncKind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextDocumentSyncKind.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/23/16. 6 | // 7 | // 8 | 9 | /// Defines how the host (editor) should sync document changes to the langauge server. 10 | public enum TextDocumentSyncKind : Int { 11 | 12 | /// Documents should not be synced at all. 13 | case None = 0 14 | 15 | /// Documents are synced by always sending the full content of the document. 16 | case Full = 1 17 | 18 | /// Documents are synced by sending the full content on open. After that only incremental 19 | /// updates to the document are sent. 20 | case Incremental = 2 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/TextEdit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextEdit.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/2/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Ogra 11 | 12 | /// A textual edit applicable to a text document. 13 | /// 14 | /// - Note: If n `TextEdit`s are applied to a text document all text edits describe changes to the 15 | /// initial document version. Execution wise text edits should applied from the bottom to the top of 16 | /// the text document. Overlapping text edits are not supported. 17 | struct TextEdit { 18 | 19 | /// The range of the text document to be manipulated. To insert text into a document create a 20 | /// range where `start === end`. 21 | let range: TextDocumentRange 22 | 23 | /// The string to be inserted. For delete operations use an empty string. 24 | let newText: String 25 | 26 | } 27 | 28 | extension TextEdit : Ogra.Encodable { 29 | 30 | func encode() -> JSON { 31 | return JSON.null 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/VersionedTextDocumentIdentifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionedTextDocumentIdentifier.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/7/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Curry 11 | import Foundation 12 | import Runes 13 | 14 | struct VersionedTextDocumentIdentifier : TextDocumentIdentifier { 15 | 16 | /// The text document's URI. 17 | let uri: URL 18 | 19 | /// The version number of this document. 20 | let version: Int 21 | 22 | } 23 | 24 | extension VersionedTextDocumentIdentifier : Argo.Decodable { 25 | 26 | static func decode(_ json: JSON) -> Decoded { 27 | let uri: Decoded = json["uri"] 28 | let version: Decoded = json["version"] 29 | return curry(VersionedTextDocumentIdentifier.init) <^> uri <*> version 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/WorkspaceError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceError.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/25/16. 6 | // 7 | // 8 | 9 | import BaseProtocol 10 | import Foundation 11 | 12 | enum WorkspaceError : ServerError { 13 | 14 | case notFound(URL) 15 | 16 | case positionNotFound 17 | 18 | case sourceKit 19 | 20 | } 21 | 22 | extension WorkspaceError { 23 | 24 | var code: Int { 25 | switch self { 26 | case .notFound(_): 27 | return -32099 28 | case .positionNotFound: 29 | return -32098 30 | case .sourceKit: 31 | return -32097 32 | } 33 | } 34 | 35 | var message: String { 36 | switch self { 37 | case .notFound(let url): 38 | return "Could not find \(url.path) in the workspace." 39 | case .positionNotFound: 40 | return "Could not find the text in the source file." 41 | case .sourceKit: 42 | return "There was an error communicating with SourceKit." 43 | } 44 | } 45 | 46 | var data: Any? { return .none } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/LanguageServerProtocol/Types/WorkspaceSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceSequence.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 10/25/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | /// Provide a type to iterate over the contents of a directory on the file system. 12 | struct WorkspaceSequence : Sequence { 13 | 14 | let root: URL 15 | 16 | func makeIterator() -> AnyIterator { 17 | guard let enumerator = FileManager.default.enumerator(at: self.root, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsHiddenFiles, errorHandler: nil) else { 18 | return AnyIterator { nil } 19 | } 20 | return AnyIterator { 21 | return enumerator.nextObject() as? URL 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Extensions/Array.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 3/18/17. 6 | // 7 | // 8 | 9 | import SourceKit 10 | 11 | extension Array where Element: SourceKitRequestable { 12 | var sourceKit: sourcekitd_object_t? { 13 | var request = self.map { $0.sourceKitObject } 14 | return sourcekitd_request_array_create(&request, request.count) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Extensions/Int64.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int64.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 3/19/17. 6 | // 7 | // 8 | 9 | import SourceKit 10 | 11 | extension Int64 : SourceKitRequestable { 12 | var sourceKitObject: sourcekitd_object_t? { 13 | return sourcekitd_request_int64_create(self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Extensions/Optional.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Optional.swift 3 | // SourceKitter 4 | // 5 | // Created by Ryan Lovelett on 4/3/18. 6 | // 7 | 8 | #if swift(>=4.2) 9 | #else 10 | extension Optional: Hashable where Wrapped: Hashable { 11 | /// The hash value for the optional instance. 12 | /// 13 | /// Two optionals that are equal will always have equal hash values. 14 | /// 15 | /// Hash values are not guaranteed to be equal across different executions of 16 | /// your program. Do not save hash values to use during a future execution. 17 | @_inlineable 18 | public var hashValue: Int { 19 | switch self { 20 | case .none: 21 | return 0 22 | case .some(let wrapped): 23 | return 1 ^ wrapped.hashValue 24 | } 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/11/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import SourceKit 11 | 12 | extension String : SourceKitRequestable { 13 | var isFile: Bool { 14 | return FileManager.default.fileExists(atPath: self) 15 | } 16 | 17 | var sourceKitObject: sourcekitd_object_t? { 18 | return sourcekitd_request_string_create(self) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Extensions/URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 3/19/17. 6 | // 7 | // 8 | 9 | import Foundation 10 | import SourceKit 11 | 12 | extension URL : SourceKitRequestable { 13 | var sourceKitObject: sourcekitd_object_t? { 14 | return sourcekitd_request_string_create(self.path) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Types/RequestType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestType.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 3/19/17. 6 | // 7 | // 8 | 9 | import SourceKit 10 | 11 | enum RequestType : SourceKitRequestable { 12 | case cursorInfo 13 | case codeComplete 14 | 15 | var sourceKitObject: sourcekitd_object_t? { 16 | let str: String 17 | switch self { 18 | case .cursorInfo: 19 | str = "source.request.cursorinfo" 20 | case .codeComplete: 21 | str = "source.request.codecomplete" 22 | } 23 | return sourcekitd_uid_get_from_cstr(str).flatMap { sourcekitd_request_uid_create($0) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Types/SourceKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceKit.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 12 | import os.log 13 | #endif 14 | import SourceKit 15 | 16 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 17 | private let log = OSLog(subsystem: "me.lovelett.langserver-swift", category: "SourceKit") 18 | #endif 19 | 20 | /// Provides a convience wrapper to safely make calls to SourceKit. 21 | public final class SourceKit { 22 | 23 | /// Initialize and maintain a connection with SourceKit for the lifetime of an instance. 24 | /// 25 | /// There should only be one of these in the entire application. 26 | public class Session { 27 | 28 | /// Create a SourceKit session. 29 | public init() { 30 | sourcekitd_initialize() 31 | } 32 | 33 | /// Exit the SourceKit session. 34 | deinit { 35 | sourcekitd_shutdown() 36 | } 37 | 38 | } 39 | 40 | /// Convert Swift native types, e.g., String, Int64, URL, to their SourceKit native types. 41 | /// 42 | /// For example, assume a SourceKit request has an option that has a key, `key.sourcetext`, that takes 43 | /// a `String`. To make such an argument would be to call `Option(uid: "key.sourcetext", value: "Text")`. 44 | /// 45 | /// When you then need to send those properties to SourceKit use the `key` and `obj` properties. 46 | /// 47 | /// - Warning: The memory returned from this stuff leaks like a sive. 48 | /// - TODO: Fix all the memory leaks in this type. 49 | struct Option { 50 | 51 | private let uid: String 52 | 53 | private let value: T 54 | 55 | init(uid: String, value: T) { 56 | self.uid = uid 57 | self.value = value 58 | } 59 | 60 | var key: sourcekitd_uid_t? { 61 | return sourcekitd_uid_get_from_cstr(uid) 62 | } 63 | 64 | var obj: sourcekitd_object_t? { 65 | return value.sourceKitObject 66 | } 67 | 68 | } 69 | 70 | private let obj: sourcekitd_object_t? 71 | 72 | /// Create a SourceKit request object from the options and build arguments provided. 73 | /// 74 | /// - Parameters: 75 | /// - options: A dictionary containing the key value pairs of options to be sent with a request. 76 | /// - args: The build arguments array. 77 | private init(_ options: [sourcekitd_uid_t? : sourcekitd_object_t?], _ args: [String]) { 78 | var dict = options 79 | dict[sourcekitd_uid_get_from_cstr("key.compilerargs")] = args.sourceKit 80 | var keys = Array(dict.keys) 81 | var values = Array(dict.values) 82 | obj = sourcekitd_request_dictionary_create(&keys, &values, dict.count) 83 | } 84 | 85 | deinit { 86 | obj.map { sourcekitd_request_release($0) } 87 | } 88 | 89 | /// Issue the request to SourceKit and parse the response into JSON. 90 | /// 91 | /// - Returns: A monad that contains the response from SourceKit. 92 | public func request() -> Decoded { 93 | #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) 94 | let descPtr = obj.flatMap { sourcekitd_request_description_copy($0) } 95 | let description = descPtr 96 | .map({ (UnsafeMutableRawPointer($0), Int(strlen($0))) }) 97 | .flatMap({ String(bytesNoCopy: $0.0, length: $0.1, encoding: .utf8, freeWhenDone: true) })! 98 | os_log("%{public}@", log: log, type: .default, description) 99 | #endif 100 | 101 | let response = sourcekitd_send_request_sync(obj!)! 102 | defer { 103 | sourcekitd_response_dispose(response) 104 | } 105 | if sourcekitd_response_is_error(response) { 106 | let error = SourceKitError(response) 107 | return Decoded.customError(error.description) 108 | } 109 | let info = sourcekitd_response_get_value(response) 110 | let ptr = sourcekitd_variant_json_description_copy(info) 111 | let json = Decoded>.fromOptional(ptr) 112 | // ⚠️ The documentation for `sourcekitd_variant_json_description_copy` says: ⚠️ 113 | // This string should be disposed of with `free` when done. 114 | // Thus the `Data` initializer with custom deallocator is used 115 | .map({ Data(bytesNoCopy: UnsafeMutableRawPointer($0), count: Int(strlen($0)), deallocator: .free) }) 116 | .flatMap({ Decoded.fromOptional(try? JSONSerialization.jsonObject(with: $0, options: [])) }) 117 | .map(JSON.init) 118 | return json 119 | } 120 | 121 | /// SourceKit is capable of providing information about a specific symbol at a specific cursor, or offset, position 122 | /// in a document. 123 | /// 124 | /// To gather documentation, SourceKit must be given either the name of a module, the path to a file, or some text. 125 | /// 126 | /// - Note: The source file is ignored when source text is also provided, and both of those keys are ignored if a 127 | /// module name is provided. 128 | /// 129 | /// - SeeAlso: SourceKit Docs: [Cursor Info](https://github.com/apple/swift/blob/ 130 | /// cf5f91437754568efce7a52e7ffb1794d2a6cc60/tools/SourceKit/docs/Protocol.md#cursor-info) 131 | /// 132 | /// - Parameters: 133 | /// - text: Source contents. 134 | /// - file: Absolute path to the file. 135 | /// - offset: Byte offset of code point inside the source contents. 136 | /// - args: Array of zero or more strings for the compiler arguments, e.g., `["-sdk", "/path/to/sdk"]`. If source 137 | /// file is provided, this array must include the path to that file. 138 | /// - Returns: A `SourceKit` instance that can be used to request a JSON formatted response. 139 | public static func CursorInfo(source text: String, source file: URL, offset: Int64, args: [String]) -> SourceKit { 140 | let request = SourceKit.Option(uid: "key.request", value: RequestType.cursorInfo) 141 | let sourceText = SourceKit.Option(uid: "key.sourcetext", value: text) 142 | let sourceFile = SourceKit.Option(uid: "key.sourcefile", value: file) 143 | let offset = SourceKit.Option(uid: "key.offset", value: offset) 144 | let options: [sourcekitd_uid_t? : sourcekitd_object_t?] = [ 145 | request.key: request.obj, 146 | sourceText.key: sourceText.obj, 147 | sourceFile.key: sourceFile.obj, 148 | offset.key: offset.obj 149 | ] 150 | return SourceKit(options, args) 151 | } 152 | 153 | /// SourceKit is capable of providing code completion suggestions. To do so, it must be given either the path to a 154 | /// file, or some text. 155 | /// 156 | /// - Note: The source file is ignored when source text is also provided. 157 | /// 158 | /// - SeeAlso: SourceKit Docs: [Code Completion](https://github.com/apple/swift/blob/ 159 | /// cf5f91437754568efce7a52e7ffb1794d2a6cc60/tools/SourceKit/docs/Protocol.md#code-completion) 160 | /// 161 | /// - Parameters: 162 | /// - text: Source contents 163 | /// - file: Absolute path to the file 164 | /// - offset: Byte offset of code-completion point inside the source contents 165 | /// - args: Array of zero or more strings for the compiler arguments, e.g., `["-sdk", "/path/to/sdk"]`. If source 166 | /// file is provided, this array must include the path to that file. 167 | /// - Returns: A `SourceKit` instance that can be used to request a JSON formatted response. 168 | public static func CodeComplete(source text: String, source file: URL, offset: Int64, args: [String]) -> SourceKit { 169 | let request = SourceKit.Option(uid: "key.request", value: RequestType.codeComplete) 170 | let sourceText = SourceKit.Option(uid: "key.sourcetext", value: text) 171 | let sourceFile = SourceKit.Option(uid: "key.sourcefile", value: file) 172 | let offset = SourceKit.Option(uid: "key.offset", value: offset) 173 | let options: [sourcekitd_uid_t? : sourcekitd_object_t?] = [ 174 | request.key: request.obj, 175 | sourceText.key: sourceText.obj, 176 | sourceFile.key: sourceFile.obj, 177 | offset.key: offset.obj 178 | ] 179 | return SourceKit(options, args) 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Types/SourceKitError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceKitError.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/11/16. 6 | // 7 | // 8 | 9 | import SourceKit 10 | 11 | /// A enum representation of `SOURCEKITD_ERROR_*` 12 | enum SourceKitError : Error, CustomStringConvertible { 13 | case connectionInterrupted(String?) 14 | case invalid(String?) 15 | case failed(String?) 16 | case cancelled(String?) 17 | case unknown(String?) 18 | 19 | init(_ response: sourcekitd_response_t) { 20 | let cStr = sourcekitd_response_error_get_description(response)! 21 | // TODO: Do I need to release the cStr? 22 | let description = String(validatingUTF8: cStr) 23 | switch sourcekitd_response_error_get_kind(response) { 24 | case SOURCEKITD_ERROR_CONNECTION_INTERRUPTED: self = .connectionInterrupted(description) 25 | case SOURCEKITD_ERROR_REQUEST_INVALID: self = .invalid(description) 26 | case SOURCEKITD_ERROR_REQUEST_FAILED: self = .failed(description) 27 | case SOURCEKITD_ERROR_REQUEST_CANCELLED: self = .cancelled(description) 28 | default: self = .unknown(description) 29 | } 30 | } 31 | 32 | var description: String { 33 | return "Do this right!" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Types/SourceKitRequestable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SourceKitRequestable.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/11/16. 6 | // 7 | // 8 | 9 | import SourceKit 10 | 11 | protocol SourceKitRequestable { 12 | var sourceKitObject: sourcekitd_object_t? { get } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SourceKitter/Types/library_wrapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // library_wrapper.swift 3 | // sourcekitten 4 | // 5 | // Created by Norio Nomura on 2/20/16. 6 | // Copyright © 2016 SourceKitten. All rights reserved. 7 | // 8 | 9 | // This code originates from the SourceKitten tool which carries the following license: 10 | // The MIT License (MIT) 11 | // 12 | // Copyright (c) 2014 JP Simard. 13 | // 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy 15 | // of this software and associated documentation files (the "Software"), to deal 16 | // in the Software without restriction, including without limitation the rights 17 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the Software is 19 | // furnished to do so, subject to the following conditions: 20 | // 21 | // The above copyright notice and this permission notice shall be included in all 22 | // copies or substantial portions of the Software. 23 | // 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | // SOFTWARE. 31 | 32 | import Foundation 33 | 34 | struct DynamicLinkLibrary { 35 | let path: String 36 | let handle: UnsafeMutableRawPointer 37 | 38 | func load(symbol: String) -> T { 39 | if let sym = dlsym(handle, symbol) { 40 | return unsafeBitCast(sym, to: T.self) 41 | } 42 | let errorString = String(validatingUTF8: dlerror()) 43 | fatalError("Finding symbol \(symbol) failed: \(errorString ?? "unknown error")") 44 | } 45 | } 46 | 47 | #if os(Linux) 48 | let toolchainLoader = Loader(searchPaths: [ 49 | linuxSourceKitLibPath, 50 | linuxFindSwiftenvActiveLibPath, 51 | linuxFindSwiftInstallationLibPath, 52 | linuxDefaultLibPath 53 | ].compactMap({ $0 })) 54 | #else 55 | let toolchainLoader = Loader(searchPaths: [ 56 | xcodeDefaultToolchainOverride, 57 | toolchainDir, 58 | xcrunFindPath, 59 | /* 60 | These search paths are used when `xcode-select -p` points to 61 | "Command Line Tools OS X for Xcode", but Xcode.app exists. 62 | */ 63 | applicationsDir?.xcodeDeveloperDir.toolchainDir, 64 | applicationsDir?.xcodeBetaDeveloperDir.toolchainDir, 65 | userApplicationsDir?.xcodeDeveloperDir.toolchainDir, 66 | userApplicationsDir?.xcodeBetaDeveloperDir.toolchainDir 67 | ].compactMap { path in 68 | if let fullPath = path?.usrLibDir, fullPath.isFile { 69 | return fullPath 70 | } 71 | return nil 72 | }) 73 | #endif 74 | 75 | struct Loader { 76 | let searchPaths: [String] 77 | 78 | func load(path: String) -> DynamicLinkLibrary { 79 | let fullPaths = searchPaths.map { $0.appending(pathComponent: path) }.filter { $0.isFile } 80 | 81 | // try all fullPaths that contains target file, 82 | // then try loading with simple path that depends resolving to DYLD 83 | for fullPath in fullPaths + [path] { 84 | if let handle = dlopen(fullPath, RTLD_LAZY) { 85 | return DynamicLinkLibrary(path: path, handle: handle) 86 | } 87 | } 88 | 89 | fatalError("Loading \(path) failed") 90 | } 91 | } 92 | 93 | private func env(_ name: String) -> String? { 94 | return ProcessInfo.processInfo.environment[name] 95 | } 96 | 97 | /// Run a process at the given (absolute) path, capture output, return outupt. 98 | private func runCommand(_ path: String, _ args: String...) -> String? { 99 | let process = Process() 100 | process.launchPath = path 101 | process.arguments = args 102 | 103 | let pipe = Pipe() 104 | process.standardOutput = pipe 105 | process.launch() 106 | 107 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 108 | process.waitUntilExit() 109 | guard let encoded = String(data: data, encoding: String.Encoding.utf8) else { 110 | return nil 111 | } 112 | 113 | let trimmed = encoded.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 114 | if trimmed.isEmpty { 115 | return nil 116 | } 117 | return trimmed 118 | } 119 | 120 | /// Returns "LINUX_SOURCEKIT_LIB_PATH" environment variable. 121 | internal let linuxSourceKitLibPath = env("LINUX_SOURCEKIT_LIB_PATH") 122 | 123 | /// If available, uses `swiftenv` to determine the user's active Swift root. 124 | internal let linuxFindSwiftenvActiveLibPath: String? = { 125 | guard let swiftenvPath = runCommand("/usr/bin/which", "swiftenv") else { 126 | return nil 127 | } 128 | 129 | guard let swiftenvRoot = runCommand(swiftenvPath, "prefix") else { 130 | return nil 131 | } 132 | 133 | return swiftenvRoot + "/usr/lib" 134 | }() 135 | 136 | /// Attempts to discover the location of libsourcekitdInProc.so by looking at 137 | /// the `swift` binary on the path. 138 | internal let linuxFindSwiftInstallationLibPath: String? = { 139 | guard let swiftPath = runCommand("/usr/bin/which", "swift") else { 140 | return nil 141 | } 142 | 143 | if linuxSourceKitLibPath == nil && linuxFindSwiftenvActiveLibPath == nil && 144 | swiftPath.hasSuffix("/shims/swift") { 145 | /// If we hit this path, the user is invoking Swift via swiftenv shims and has not set the 146 | /// environment variable; this means we're going to end up trying to load from `/usr/lib` 147 | /// which will fail - and instead, we can give a more useful error message. 148 | fatalError("Swift is installed via swiftenv but swiftenv is not initialized.") 149 | } 150 | 151 | if !swiftPath.hasSuffix("/bin/swift") { 152 | return nil 153 | } 154 | 155 | /// .../bin/swift -> .../lib 156 | return swiftPath.deleting(lastPathComponents: 2) + "lib" 157 | }() 158 | 159 | /// Fallback path on Linux if no better option is available. 160 | internal let linuxDefaultLibPath = "/usr/lib" 161 | 162 | /// Returns "XCODE_DEFAULT_TOOLCHAIN_OVERRIDE" environment variable 163 | /// 164 | /// `launch-with-toolchain` sets the toolchain path to the 165 | /// "XCODE_DEFAULT_TOOLCHAIN_OVERRIDE" environment variable. 166 | private let xcodeDefaultToolchainOverride = env("XCODE_DEFAULT_TOOLCHAIN_OVERRIDE") 167 | 168 | /// Returns "TOOLCHAIN_DIR" environment variable 169 | /// 170 | /// `Xcode`/`xcodebuild` sets the toolchain path to the 171 | /// "TOOLCHAIN_DIR" environment variable. 172 | private let toolchainDir = env("TOOLCHAIN_DIR") 173 | 174 | /// Returns toolchain directory that parsed from result of `xcrun -find swift` 175 | /// 176 | /// This is affected by "DEVELOPER_DIR", "TOOLCHAINS" environment variables. 177 | private let xcrunFindPath: String? = { 178 | let pathOfXcrun = "/usr/bin/xcrun" 179 | 180 | if !FileManager.default.isExecutableFile(atPath: pathOfXcrun) { 181 | return nil 182 | } 183 | 184 | let task = Process() 185 | task.launchPath = pathOfXcrun 186 | task.arguments = ["-find", "swift"] 187 | 188 | let pipe = Pipe() 189 | task.standardOutput = pipe 190 | task.launch() // if xcode-select does not exist, crash with `NSInvalidArgumentException`. 191 | 192 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 193 | guard let output = String(data: data, encoding: .utf8) else { 194 | return nil 195 | } 196 | 197 | var start = output.startIndex 198 | var end = output.startIndex 199 | var contentsEnd = output.startIndex 200 | output.getLineStart(&start, end: &end, contentsEnd: &contentsEnd, for: start..=4.0) 202 | let xcrunFindSwiftPath = String(output[start.. String { 242 | return URL(fileURLWithPath: self).appendingPathComponent(pathComponent).path 243 | } 244 | 245 | func deleting(lastPathComponents numberOfPathComponents: Int) -> String { 246 | var url = URL(fileURLWithPath: self) 247 | for _ in 0.. () = library.load(symbol: "sourcekitd_initialize") 34 | internal let sourcekitd_shutdown: @convention(c) () -> () = library.load(symbol: "sourcekitd_shutdown") 35 | internal let sourcekitd_set_interrupted_connection_handler: @convention(c) (@escaping sourcekitd_interrupted_connection_handler_t) -> () = library.load(symbol: "sourcekitd_set_interrupted_connection_handler") 36 | internal let sourcekitd_uid_get_from_cstr: @convention(c) (UnsafePointer) -> (sourcekitd_uid_t?) = library.load(symbol: "sourcekitd_uid_get_from_cstr") 37 | internal let sourcekitd_uid_get_from_buf: @convention(c) (UnsafePointer, Int) -> (sourcekitd_uid_t?) = library.load(symbol: "sourcekitd_uid_get_from_buf") 38 | internal let sourcekitd_uid_get_length: @convention(c) (sourcekitd_uid_t) -> (Int) = library.load(symbol: "sourcekitd_uid_get_length") 39 | internal let sourcekitd_uid_get_string_ptr: @convention(c) (sourcekitd_uid_t) -> (UnsafePointer?) = library.load(symbol: "sourcekitd_uid_get_string_ptr") 40 | internal let sourcekitd_request_retain: @convention(c) (sourcekitd_object_t) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_retain") 41 | internal let sourcekitd_request_release: @convention(c) (sourcekitd_object_t) -> () = library.load(symbol: "sourcekitd_request_release") 42 | internal let sourcekitd_request_dictionary_create: @convention(c) (UnsafePointer?, UnsafePointer?, Int) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_dictionary_create") 43 | internal let sourcekitd_request_dictionary_set_value: @convention(c) (sourcekitd_object_t, sourcekitd_uid_t, sourcekitd_object_t) -> () = library.load(symbol: "sourcekitd_request_dictionary_set_value") 44 | internal let sourcekitd_request_dictionary_set_string: @convention(c) (sourcekitd_object_t, sourcekitd_uid_t, UnsafePointer) -> () = library.load(symbol: "sourcekitd_request_dictionary_set_string") 45 | internal let sourcekitd_request_dictionary_set_stringbuf: @convention(c) (sourcekitd_object_t, sourcekitd_uid_t, UnsafePointer, Int) -> () = library.load(symbol: "sourcekitd_request_dictionary_set_stringbuf") 46 | internal let sourcekitd_request_dictionary_set_int64: @convention(c) (sourcekitd_object_t, sourcekitd_uid_t, Int64) -> () = library.load(symbol: "sourcekitd_request_dictionary_set_int64") 47 | internal let sourcekitd_request_dictionary_set_uid: @convention(c) (sourcekitd_object_t, sourcekitd_uid_t, sourcekitd_uid_t) -> () = library.load(symbol: "sourcekitd_request_dictionary_set_uid") 48 | internal let sourcekitd_request_array_create: @convention(c) (UnsafePointer?, Int) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_array_create") 49 | internal let sourcekitd_request_array_set_value: @convention(c) (sourcekitd_object_t, Int, sourcekitd_object_t) -> () = library.load(symbol: "sourcekitd_request_array_set_value") 50 | internal let sourcekitd_request_array_set_string: @convention(c) (sourcekitd_object_t, Int, UnsafePointer) -> () = library.load(symbol: "sourcekitd_request_array_set_string") 51 | internal let sourcekitd_request_array_set_stringbuf: @convention(c) (sourcekitd_object_t, Int, UnsafePointer, Int) -> () = library.load(symbol: "sourcekitd_request_array_set_stringbuf") 52 | internal let sourcekitd_request_array_set_int64: @convention(c) (sourcekitd_object_t, Int, Int64) -> () = library.load(symbol: "sourcekitd_request_array_set_int64") 53 | internal let sourcekitd_request_array_set_uid: @convention(c) (sourcekitd_object_t, Int, sourcekitd_uid_t) -> () = library.load(symbol: "sourcekitd_request_array_set_uid") 54 | internal let sourcekitd_request_int64_create: @convention(c) (Int64) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_int64_create") 55 | internal let sourcekitd_request_string_create: @convention(c) (UnsafePointer) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_string_create") 56 | internal let sourcekitd_request_uid_create: @convention(c) (sourcekitd_uid_t) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_uid_create") 57 | internal let sourcekitd_request_create_from_yaml: @convention(c) (UnsafePointer, UnsafeMutablePointer?>?) -> (sourcekitd_object_t?) = library.load(symbol: "sourcekitd_request_create_from_yaml") 58 | internal let sourcekitd_request_description_dump: @convention(c) (sourcekitd_object_t) -> () = library.load(symbol: "sourcekitd_request_description_dump") 59 | internal let sourcekitd_request_description_copy: @convention(c) (sourcekitd_object_t) -> (UnsafeMutablePointer?) = library.load(symbol: "sourcekitd_request_description_copy") 60 | internal let sourcekitd_response_dispose: @convention(c) (sourcekitd_response_t) -> () = library.load(symbol: "sourcekitd_response_dispose") 61 | internal let sourcekitd_response_is_error: @convention(c) (sourcekitd_response_t) -> (Bool) = library.load(symbol: "sourcekitd_response_is_error") 62 | internal let sourcekitd_response_error_get_kind: @convention(c) (sourcekitd_response_t) -> (sourcekitd_error_t) = library.load(symbol: "sourcekitd_response_error_get_kind") 63 | internal let sourcekitd_response_error_get_description: @convention(c) (sourcekitd_response_t) -> (UnsafePointer?) = library.load(symbol: "sourcekitd_response_error_get_description") 64 | internal let sourcekitd_response_get_value: @convention(c) (sourcekitd_response_t) -> (sourcekitd_variant_t) = library.load(symbol: "sourcekitd_response_get_value") 65 | internal let sourcekitd_variant_get_type: @convention(c) (sourcekitd_variant_t) -> (sourcekitd_variant_type_t) = library.load(symbol: "sourcekitd_variant_get_type") 66 | internal let sourcekitd_variant_dictionary_get_value: @convention(c) (sourcekitd_variant_t, sourcekitd_uid_t) -> (sourcekitd_variant_t) = library.load(symbol: "sourcekitd_variant_dictionary_get_value") 67 | internal let sourcekitd_variant_dictionary_get_string: @convention(c) (sourcekitd_variant_t, sourcekitd_uid_t) -> (UnsafePointer?) = library.load(symbol: "sourcekitd_variant_dictionary_get_string") 68 | internal let sourcekitd_variant_dictionary_get_int64: @convention(c) (sourcekitd_variant_t, sourcekitd_uid_t) -> (Int64) = library.load(symbol: "sourcekitd_variant_dictionary_get_int64") 69 | internal let sourcekitd_variant_dictionary_get_bool: @convention(c) (sourcekitd_variant_t, sourcekitd_uid_t) -> (Bool) = library.load(symbol: "sourcekitd_variant_dictionary_get_bool") 70 | internal let sourcekitd_variant_dictionary_get_uid: @convention(c) (sourcekitd_variant_t, sourcekitd_uid_t) -> (sourcekitd_uid_t?) = library.load(symbol: "sourcekitd_variant_dictionary_get_uid") 71 | internal let sourcekitd_variant_dictionary_apply_f: @convention(c) (sourcekitd_variant_t, @escaping sourcekitd_variant_dictionary_applier_f_t, UnsafeMutableRawPointer?) -> (Bool) = library.load(symbol: "sourcekitd_variant_dictionary_apply_f") 72 | internal let sourcekitd_variant_array_get_count: @convention(c) (sourcekitd_variant_t) -> (Int) = library.load(symbol: "sourcekitd_variant_array_get_count") 73 | internal let sourcekitd_variant_array_get_value: @convention(c) (sourcekitd_variant_t, Int) -> (sourcekitd_variant_t) = library.load(symbol: "sourcekitd_variant_array_get_value") 74 | internal let sourcekitd_variant_array_get_string: @convention(c) (sourcekitd_variant_t, Int) -> (UnsafePointer?) = library.load(symbol: "sourcekitd_variant_array_get_string") 75 | internal let sourcekitd_variant_array_get_int64: @convention(c) (sourcekitd_variant_t, Int) -> (Int64) = library.load(symbol: "sourcekitd_variant_array_get_int64") 76 | internal let sourcekitd_variant_array_get_bool: @convention(c) (sourcekitd_variant_t, Int) -> (Bool) = library.load(symbol: "sourcekitd_variant_array_get_bool") 77 | internal let sourcekitd_variant_array_get_uid: @convention(c) (sourcekitd_variant_t, Int) -> (sourcekitd_uid_t?) = library.load(symbol: "sourcekitd_variant_array_get_uid") 78 | internal let sourcekitd_variant_array_apply_f: @convention(c) (sourcekitd_variant_t, @escaping sourcekitd_variant_array_applier_f_t, UnsafeMutableRawPointer?) -> (Bool) = library.load(symbol: "sourcekitd_variant_array_apply_f") 79 | internal let sourcekitd_variant_int64_get_value: @convention(c) (sourcekitd_variant_t) -> (Int64) = library.load(symbol: "sourcekitd_variant_int64_get_value") 80 | internal let sourcekitd_variant_bool_get_value: @convention(c) (sourcekitd_variant_t) -> (Bool) = library.load(symbol: "sourcekitd_variant_bool_get_value") 81 | internal let sourcekitd_variant_string_get_length: @convention(c) (sourcekitd_variant_t) -> (Int) = library.load(symbol: "sourcekitd_variant_string_get_length") 82 | internal let sourcekitd_variant_string_get_ptr: @convention(c) (sourcekitd_variant_t) -> (UnsafePointer?) = library.load(symbol: "sourcekitd_variant_string_get_ptr") 83 | internal let sourcekitd_variant_uid_get_value: @convention(c) (sourcekitd_variant_t) -> (sourcekitd_uid_t?) = library.load(symbol: "sourcekitd_variant_uid_get_value") 84 | internal let sourcekitd_response_description_dump: @convention(c) (sourcekitd_response_t) -> () = library.load(symbol: "sourcekitd_response_description_dump") 85 | internal let sourcekitd_response_description_dump_filedesc: @convention(c) (sourcekitd_response_t, Int32) -> () = library.load(symbol: "sourcekitd_response_description_dump_filedesc") 86 | internal let sourcekitd_response_description_copy: @convention(c) (sourcekitd_response_t) -> (UnsafeMutablePointer?) = library.load(symbol: "sourcekitd_response_description_copy") 87 | internal let sourcekitd_variant_description_dump: @convention(c) (sourcekitd_variant_t) -> () = library.load(symbol: "sourcekitd_variant_description_dump") 88 | internal let sourcekitd_variant_description_dump_filedesc: @convention(c) (sourcekitd_variant_t, Int32) -> () = library.load(symbol: "sourcekitd_variant_description_dump_filedesc") 89 | internal let sourcekitd_variant_description_copy: @convention(c) (sourcekitd_variant_t) -> (UnsafeMutablePointer?) = library.load(symbol: "sourcekitd_variant_description_copy") 90 | internal let sourcekitd_variant_json_description_copy: @convention(c) (sourcekitd_variant_t) -> (UnsafeMutablePointer?) = library.load(symbol: "sourcekitd_variant_json_description_copy") 91 | internal let sourcekitd_send_request_sync: @convention(c) (sourcekitd_object_t) -> (sourcekitd_response_t?) = library.load(symbol: "sourcekitd_send_request_sync") 92 | internal let sourcekitd_send_request: @convention(c) (sourcekitd_object_t, UnsafeMutablePointer?, sourcekitd_response_receiver_t?) -> () = library.load(symbol: "sourcekitd_send_request") 93 | internal let sourcekitd_cancel_request: @convention(c) (sourcekitd_request_handle_t?) -> () = library.load(symbol: "sourcekitd_cancel_request") 94 | internal let sourcekitd_set_notification_handler: @convention(c) (sourcekitd_response_receiver_t?) -> () = library.load(symbol: "sourcekitd_set_notification_handler") 95 | internal let sourcekitd_set_uid_handler: @convention(c) (sourcekitd_uid_handler_t?) -> () = library.load(symbol: "sourcekitd_set_uid_handler") 96 | internal let sourcekitd_set_uid_handlers: @convention(c) (sourcekitd_uid_from_str_handler_t?, sourcekitd_str_from_uid_handler_t?) -> () = library.load(symbol: "sourcekitd_set_uid_handlers") 97 | -------------------------------------------------------------------------------- /Tests/BaseProtocolTests/Resources/Fixtures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fixtures.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | 12 | fileprivate func loadData(_ url: URL) -> Data? { 13 | return try? Data(contentsOf: url, options: .mappedIfSafe) 14 | } 15 | 16 | fileprivate func serializeJSON(_ data: Data) -> Any? { 17 | return try? JSONSerialization.jsonObject(with: data, options: []) 18 | } 19 | 20 | let root: URL = { 21 | var u = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponent() 22 | while u.lastPathComponent != "langserver-swift" { 23 | u.deleteLastPathComponent() 24 | } 25 | return u 26 | }() 27 | 28 | func getFixture(subdirectory: String) -> URL { 29 | return root 30 | .appendingPathComponent("Fixtures", isDirectory: true) 31 | .appendingPathComponent(subdirectory, isDirectory: true) 32 | } 33 | 34 | func getFixture(_ named: String, in subdirectory: String) -> URL? { 35 | return getFixture(subdirectory: subdirectory) 36 | .appendingPathComponent(named, isDirectory: false) 37 | } 38 | 39 | func loadFixture(_ named: String, in subdirectory: String) -> Data? { 40 | return getFixture(named, in: subdirectory).flatMap(loadData) 41 | } 42 | 43 | func loadJSONFromFixture(_ named: String, in subdirectory: String = "JSON") -> JSON { 44 | return loadFixture(named, in: subdirectory) 45 | .flatMap(serializeJSON) 46 | .map({ JSON($0) }) ?? .null 47 | } 48 | -------------------------------------------------------------------------------- /Tests/BaseProtocolTests/Types/HeaderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HeaderTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/8/16. 6 | // 7 | // 8 | 9 | @testable import BaseProtocol 10 | import XCTest 11 | 12 | class HeaderTests: XCTestCase { 13 | 14 | func testHeaderWithNumericContentLength() { 15 | let d = loadFixture("shutdown.txt", in: "JSON-RPC/Requests")! 16 | let header = Header(d) 17 | XCTAssertEqual(header.contentLength, 58) 18 | } 19 | 20 | func testHeaderWithoutContentLength() { 21 | let d = loadFixture("non-numeric-content-length.txt", in: "JSON-RPC/Requests")! 22 | let header = Header(d) 23 | XCTAssertNil(header.contentLength) 24 | } 25 | 26 | func testHeaderWithMultipleFields() { 27 | let d = loadFixture("multiple-field-header.txt", in: "JSON-RPC/Requests")! 28 | var it = Header(d) 29 | 30 | // Content-Length: 271 31 | let contentLength = it.next() 32 | XCTAssertEqual(contentLength?.name, "Content-Length") 33 | XCTAssertEqual(contentLength?.body, "271") 34 | 35 | // Content-Type: application/vscode-jsonrpc; charset=utf8 36 | let contentType = it.next() 37 | XCTAssertEqual(contentType?.name, "Content-Type") 38 | XCTAssertEqual(contentType?.body, "application/vscode-jsonrpc; charset=utf8") 39 | 40 | // Date: Thu, 08 Dec 2016 18:41:04 GMT 41 | let date = it.next() 42 | XCTAssertEqual(date?.name, "Date") 43 | XCTAssertEqual(date?.body, "Thu, 08 Dec 2016 18:41:04 GMT") 44 | 45 | // 🏁 46 | XCTAssertNil(it.next()) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Tests/BaseProtocolTests/Types/RequestBufferTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestIteratorTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/8/16. 6 | // 7 | // 8 | 9 | @testable import BaseProtocol 10 | import XCTest 11 | 12 | class RequestIteratorTests: XCTestCase { 13 | 14 | func testIteratingMultipleRequestsAndHeaders() { 15 | let d = loadFixture("multiple-requests-and-headers.txt", in: "JSON-RPC/Requests")! 16 | let c = Array(AnySequence { RequestBuffer(d) }) 17 | 18 | XCTAssertEqual(c.count, 4) 19 | XCTAssertEqual(c.map({ $0.count }), [270, 62, 271, 222]) 20 | } 21 | 22 | func testIteratingFullAndPartialRequestsWithoutCrash() { 23 | let d = loadFixture("partial-request.txt", in: "JSON-RPC/Requests")! 24 | let it = RequestBuffer(d) 25 | 26 | let first = it.next() 27 | XCTAssertEqual(first?.count, 282) 28 | 29 | XCTAssertNil(it.next()) 30 | XCTAssertNil(it.next()) 31 | XCTAssertNil(it.next()) 32 | XCTAssertNil(it.next()) 33 | } 34 | 35 | func testIteratingByAppendingBuffers() { 36 | let d1 = loadFixture("partial-request-1.txt", in: "JSON-RPC/Requests")! 37 | let d2 = loadFixture("partial-request-2.txt", in: "JSON-RPC/Requests")! 38 | let d3 = loadFixture("partial-request-3.txt", in: "JSON-RPC/Requests")! 39 | 40 | let it = RequestBuffer(Data()) 41 | // Since no data was sent to start it should have nothing in it 42 | XCTAssertNil(it.next()) 43 | XCTAssertNil(it.next()) 44 | 45 | it.append(d1) 46 | // Since d1 itself is partial it should have nothing to return 47 | XCTAssertNil(it.next()) 48 | XCTAssertNil(it.next()) 49 | XCTAssertNil(it.next()) 50 | XCTAssertNil(it.next()) 51 | 52 | it.append(d2) 53 | XCTAssertEqual(it.next()?.count, 355) 54 | XCTAssertEqual(it.next()?.count, 223) 55 | XCTAssertNil(it.next()) 56 | XCTAssertNil(it.next()) 57 | XCTAssertNil(it.next()) 58 | XCTAssertNil(it.next()) 59 | 60 | it.append(d3) 61 | XCTAssertEqual(it.next()?.count, 358) 62 | XCTAssertNil(it.next()) 63 | XCTAssertNil(it.next()) 64 | XCTAssertNil(it.next()) 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Tests/BaseProtocolTests/Types/RequestTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RequestTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import BaseProtocol 11 | import Curry 12 | import Runes 13 | import XCTest 14 | 15 | private struct ArrayParams : Argo.Decodable { 16 | 17 | let x: [Int] 18 | 19 | static func decode(_ json: JSON) -> Decoded { 20 | let a: Decoded<[Int]> = [Int].decode(json) 21 | return curry(ArrayParams.init) <^> a 22 | } 23 | 24 | } 25 | 26 | fileprivate let invalid = loadFixture("invalid-json.txt", in: "JSON-RPC/JSON") 27 | fileprivate let invalidRequest = loadFixture("invalid-request.txt", in: "JSON-RPC/JSON") 28 | fileprivate let invalidHeader = loadFixture("invalid-header.txt", in: "JSON-RPC/JSON") 29 | fileprivate let shutdown = loadFixture("shutdown.txt", in: "JSON-RPC/JSON") 30 | fileprivate let valid = loadFixture("valid.txt", in: "JSON-RPC/JSON") 31 | fileprivate let valid2 = loadFixture("valid-2.txt", in: "JSON-RPC/JSON") 32 | fileprivate let validWithoutHeader = loadFixture("valid-without-header.txt", in: "JSON-RPC/JSON") 33 | fileprivate let textDocumentDidOpen = loadFixture("textDocument-didOpen.txt", in: "JSON-RPC/JSON") 34 | 35 | class RequestTests: XCTestCase { 36 | 37 | override func setUp() { 38 | super.setUp() 39 | // Put setup code here. This method is called before the invocation of each test method in the class. 40 | } 41 | 42 | override func tearDown() { 43 | // Put teardown code here. This method is called after the invocation of each test method in the class. 44 | super.tearDown() 45 | } 46 | 47 | func testNotJSONContent() { 48 | let message = invalid! 49 | 50 | do { 51 | _ = try Request(message) 52 | } catch let error as PredefinedError { 53 | XCTAssertEqual(error, PredefinedError.parse) 54 | } catch { 55 | XCTFail("Expected a JSON parsing error.") 56 | } 57 | } 58 | 59 | func testInvalidRequestJSON() { 60 | let message = invalidRequest! 61 | 62 | do { 63 | _ = try Request(message) 64 | } catch let error as PredefinedError { 65 | XCTAssertEqual(error, PredefinedError.invalidRequest) 66 | } catch { 67 | XCTFail("Should have been an invalid request.") 68 | } 69 | } 70 | 71 | func testValidRequest() { 72 | let message = validWithoutHeader! 73 | 74 | do { 75 | _ = try Request(message) 76 | } catch { 77 | XCTFail("Should have been a valid Request.") 78 | } 79 | } 80 | 81 | func testInvalidHeader() { 82 | let message = invalidHeader! 83 | 84 | do { 85 | _ = try Request(message) 86 | } catch let error as PredefinedError { 87 | XCTAssertEqual(error, PredefinedError.parse) 88 | } catch { 89 | XCTFail("Expected a JSON parsing error.") 90 | } 91 | } 92 | 93 | func testValidHeaderAndRequest() { 94 | let message = valid! 95 | 96 | do { 97 | let r = try Request(message) 98 | XCTAssertEqual(r.method, "subtract") 99 | XCTAssertEqual(r.id, Request.Identifier.number(1)) 100 | } catch { 101 | XCTFail("Should have been a valid Request.") 102 | } 103 | } 104 | 105 | func testParsingParameters() { 106 | let message = valid! 107 | 108 | do { 109 | let r = try Request(message) 110 | let p: ArrayParams = try r.parse() 111 | XCTAssertEqual(p.x, [42, 23]) 112 | } catch { 113 | XCTFail("Should have been a valid Request.") 114 | } 115 | } 116 | 117 | func testParsingIncorrectParameters() { 118 | let message = valid2! 119 | 120 | do { 121 | let r = try Request(message) 122 | let _: ArrayParams = try r.parse() 123 | XCTFail("Expected the parameters to be invalid.") 124 | } catch let error as PredefinedError { 125 | XCTAssertEqual(error, PredefinedError.invalidParams) 126 | } catch { 127 | XCTFail("Expected the parameters to be invalid.") 128 | } 129 | } 130 | 131 | func testShutdownRequest() { 132 | let message = shutdown! 133 | 134 | do { 135 | let r = try Request(message) 136 | XCTAssertEqual(r.method, "shutdown") 137 | XCTAssertEqual(r.id, Request.Identifier.number(1)) 138 | } catch { 139 | XCTFail("Should have been a valid Request.") 140 | } 141 | } 142 | 143 | func testTextDocumentDidOpen() { 144 | let message = textDocumentDidOpen! 145 | 146 | do { 147 | let r = try Request(message) 148 | XCTAssertEqual(r.method, "textDocument/didOpen") 149 | XCTAssertNil(r.id) 150 | } catch { 151 | XCTFail("Should have been a valid Request.") 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /Tests/BaseProtocolTests/Types/ResponseTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | @testable import BaseProtocol 11 | import Foundation 12 | import XCTest 13 | 14 | fileprivate func stringify(_ data: Data) -> String { 15 | return String(data: data, encoding: .utf8) ?? "Could not convert to UTF-8" 16 | } 17 | 18 | class ResponseTests: XCTestCase { 19 | 20 | func testSendingErrorMessage() { 21 | let request = Request.request(id: .number(1), method: "", params: JSON.null) 22 | let response = Response(to: request, is: PredefinedError.internalError) 23 | let message = stringify(response.data([:])) 24 | XCTAssert(message.contains("Content-Length: 75\r\n")) 25 | XCTAssert(message.contains("\"id\":1")) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Extensions/URLTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/24/16. 6 | // 7 | // 8 | 9 | import Argo 10 | @testable import LanguageServerProtocol 11 | import XCTest 12 | 13 | class URLTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testDecodeDirectory() { 26 | continueAfterFailure = false 27 | func test(_ str: String, _ expected: String? = nil) { 28 | let json = JSON.string(str) 29 | switch URL.decode(json) { 30 | case .success(let url): 31 | XCTAssertEqual(url, URL(fileURLWithPath: expected ?? str, isDirectory: true)) 32 | case .failure(let error): 33 | XCTFail(error.description) 34 | } 35 | } 36 | test("/Users/ryan/Desktop/foo/bar") 37 | test("/Users/ryan/Desktop/foo/bar/") 38 | test("file:///Users/ryan/Desktop/foo/bar", "/Users/ryan/Desktop/foo/bar") 39 | test("file:///Users/ryan/Desktop/foo/bar/", "/Users/ryan/Desktop/foo/bar") 40 | } 41 | 42 | func testDecodeFile() { 43 | continueAfterFailure = false 44 | func test(_ str: String, _ expected: String? = nil) { 45 | let json = JSON.string(str) 46 | switch URL.decode(json) { 47 | case .success(let url): 48 | XCTAssertEqual(url, URL(fileURLWithPath: expected ?? str, isDirectory: false)) 49 | case .failure(let error): 50 | XCTFail(error.description) 51 | } 52 | } 53 | test("/Users/ryan/Desktop/foo.swift") 54 | test("/Users/ryan/Desktop/foo/bar.swift") 55 | test("file:///Users/ryan/Desktop/foo.swift", "/Users/ryan/Desktop/foo.swift") 56 | test("file:///Users/ryan/Desktop/foo/bar.swift", "/Users/ryan/Desktop/foo/bar.swift") 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Functions/ConvertTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvertTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/10/16. 6 | // 7 | // 8 | 9 | @testable import LanguageServerProtocol 10 | import XCTest 11 | 12 | class ConvertTests: XCTestCase { 13 | 14 | func testExample() { 15 | XCTAssertEqual(convert("<#Ryan>").value, "<#Ryan>") 16 | XCTAssertEqual(convert("fatalError(<#T##message: String##String#>)").value, "fatalError(${1:message: String})") 17 | XCTAssertEqual(convert("x: <#T##Int#>, y: <#T##String#>)").value, "x: ${1:Int}, y: ${2:String})") 18 | XCTAssertEqual(convert("debugPrint(<#T##items: Any...##Any#>, to: &<#T##Target#>)").value, "debugPrint(${1:items: Any...}, to: &${2:Target})") 19 | XCTAssertEqual(convert("isKnownUniquelyReferenced(&<#T##object: T?##T?#>)").value, "isKnownUniquelyReferenced(&${1:object: T?})") 20 | XCTAssertEqual(convert("getVaList(<#T##args: [CVarArg]##[CVarArg]#>)").value, "getVaList(${1:args: [CVarArg]})") 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Resources/Fixtures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fixtures.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | 12 | fileprivate func loadData(_ url: URL) -> Data? { 13 | return try? Data(contentsOf: url, options: .mappedIfSafe) 14 | } 15 | 16 | fileprivate func serializeJSON(_ data: Data) -> Any? { 17 | return try? JSONSerialization.jsonObject(with: data, options: []) 18 | } 19 | 20 | let root: URL = { 21 | var u = URL(fileURLWithPath: #file, isDirectory: false).deletingLastPathComponent() 22 | while u.lastPathComponent != "langserver-swift" { 23 | u.deleteLastPathComponent() 24 | } 25 | return u 26 | }() 27 | 28 | func getFixture(subdirectory: String) -> URL { 29 | return root 30 | .appendingPathComponent("Fixtures", isDirectory: true) 31 | .appendingPathComponent(subdirectory, isDirectory: true) 32 | } 33 | 34 | func getFixture(_ named: String, in subdirectory: String) -> URL? { 35 | return getFixture(subdirectory: subdirectory) 36 | .appendingPathComponent(named, isDirectory: false) 37 | } 38 | 39 | func loadFixture(_ named: String, in subdirectory: String) -> Data? { 40 | return getFixture(named, in: subdirectory).flatMap(loadData) 41 | } 42 | 43 | func loadJSONFromFixture(_ named: String, in subdirectory: String = "JSON") -> JSON { 44 | return loadFixture(named, in: subdirectory) 45 | .flatMap(serializeJSON) 46 | .map({ JSON($0) }) ?? .null 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/CompletionItemTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompletionItemTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/2/16. 6 | // 7 | // 8 | 9 | import Argo 10 | import Foundation 11 | @testable import LanguageServerProtocol 12 | import Runes 13 | import XCTest 14 | 15 | class CompletionItemTests: XCTestCase { 16 | 17 | override func setUp() { 18 | super.setUp() 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | super.tearDown() 25 | } 26 | 27 | func testParseCompletionItems() { 28 | let json = loadJSONFromFixture("complete.json", in: "JSON/code-completion") 29 | let foo: Decoded<[CompletionItem]> = json["key.results"] 30 | 31 | switch foo { 32 | case .success(let bar): 33 | XCTAssertEqual(bar.count, 345) 34 | case .failure(let e): 35 | XCTFail(e.localizedDescription) 36 | } 37 | } 38 | 39 | func testKeywordCompletionItem() { 40 | let json = loadJSONFromFixture("import.json", in: "JSON/code-completion") 41 | let foo = CompletionItem.decode(json) 42 | switch foo { 43 | case .success(let item): 44 | XCTAssertEqual(item.kind, CompletionItemKind.Keyword) 45 | XCTAssertEqual(item.label, "import") 46 | XCTAssertEqual(item.detail, "import") 47 | XCTAssertNil(item.documentation) 48 | case .failure(let e): 49 | XCTFail(e.description) 50 | } 51 | } 52 | 53 | func testProtocolCompletionItem() { 54 | let json = loadJSONFromFixture("integer.json", in: "JSON/code-completion") 55 | let foo = CompletionItem.decode(json) 56 | switch foo { 57 | case .success(let item): 58 | XCTAssertEqual(item.kind, CompletionItemKind.Interface) 59 | XCTAssertEqual(item.label, "Integer") 60 | XCTAssertEqual(item.detail, "Integer") 61 | XCTAssertEqual(item.documentation, "A set of common requirements for Swift’s integer types.") 62 | case .failure(let e): 63 | XCTFail(e.description) 64 | } 65 | } 66 | 67 | func testConstructorCompletionItem() { 68 | let json = loadJSONFromFixture("constructor.json", in: "JSON/code-completion") 69 | let foo = CompletionItem.decode(json) 70 | switch foo { 71 | case .success(let item): 72 | XCTAssertEqual(item.kind, CompletionItemKind.Constructor) 73 | XCTAssertEqual(item.label, "(x: Int, y: String)") 74 | XCTAssertEqual(item.detail, "Test.Bar (x: Int, y: String)") 75 | XCTAssertEqual(item.insertText, "x: ${1:Int}, y: ${2:String})") 76 | XCTAssertEqual(item.insertTextFormat, .snippet) 77 | XCTAssertNil(item.documentation) 78 | case .failure(let e): 79 | XCTFail(e.description) 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/CursorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CursorTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/17/16. 6 | // 7 | // 8 | 9 | @testable import LanguageServerProtocol 10 | import XCTest 11 | 12 | class CursorTests: XCTestCase { 13 | 14 | func testSystemSymbol() { 15 | let json = loadJSONFromFixture("system.json", in: "JSON/cursor") 16 | switch Cursor.decode(json) { 17 | case .success(let c): 18 | switch c.defined { 19 | case .local(_): 20 | XCTFail("Expected a system symbol.") 21 | case let .system(moduleName, groupName, isSystem): 22 | XCTAssertEqual(moduleName, "Swift") 23 | XCTAssertEqual(groupName, "String") 24 | XCTAssertEqual(isSystem, true) 25 | } 26 | case .failure(let e): 27 | XCTFail(e.description) 28 | } 29 | } 30 | 31 | func testModuleSymbol() { 32 | let json = loadJSONFromFixture("module.json", in: "JSON/cursor") 33 | switch Cursor.decode(json) { 34 | case .success(let c): 35 | switch c.defined { 36 | case let .local(filepath, offset, length): 37 | XCTAssertTrue(filepath.path.contains("main.swift")) 38 | XCTAssertEqual(offset, 5) 39 | XCTAssertEqual(length, 23) 40 | case .system(_): 41 | XCTFail("Expected a local symbol.") 42 | } 43 | case .failure(let e): 44 | XCTFail(e.description) 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/DidChangeWatchedFilesParamsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DidChangeWatchedFilesParamsTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/17/16. 6 | // 7 | // 8 | 9 | @testable import LanguageServerProtocol 10 | import XCTest 11 | 12 | class DidChangeWatchedFilesParamsTests: XCTestCase { 13 | 14 | func testCreatedFile() { 15 | let json = loadJSONFromFixture("workspace-didChangeWatchedFiles-created.json", in: "JSON-RPC/JSON") 16 | switch DidChangeWatchedFilesParams.decode(json) { 17 | case .success(let params): 18 | XCTAssertEqual(params.changes.first?.type, FileChangeType.Created) 19 | case .failure(let error): 20 | XCTFail(error.description) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/LineCollectionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LineCollectionTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/22/16. 6 | // 7 | // 8 | 9 | import XCTest 10 | @testable import LanguageServerProtocol 11 | 12 | class LineCollectionTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testOffsetCalculation() { 25 | let url = getFixture("bar.swift", in: "ValidLayouts/Simple/Sources")! 26 | 27 | do { 28 | let lc = try LineCollection(for: url) 29 | 30 | XCTAssertEqual(lc.lines.count, 14) 31 | 32 | // All should be valid 33 | XCTAssertEqual(try lc.byteOffset(at: Position(line: 0, character: 0)), 0) // First byte 34 | XCTAssertEqual(try lc.byteOffset(at: Position(line: 5, character: 0)), 49) 35 | XCTAssertEqual(try lc.byteOffset(at: Position(line: 5, character: 14)), 63) 36 | XCTAssertEqual(try lc.byteOffset(at: Position(line: 11, character: 8)), 123) 37 | XCTAssertEqual(try lc.byteOffset(at: Position(line: 13, character: 1)), 153) // Last byte 38 | 39 | // Off the "right" edge of the first line 40 | XCTAssertNil(try? lc.byteOffset(at: Position(line: 0, character: 13))) 41 | 42 | // Off the "bottom" edge of the document 43 | XCTAssertNil(try? lc.byteOffset(at: Position(line: 15, character: 0))) 44 | } catch { 45 | XCTFail(error.localizedDescription) 46 | } 47 | } 48 | 49 | func testPositionCalculation() { 50 | let url = getFixture("bar.swift", in: "ValidLayouts/Simple/Sources")! 51 | 52 | do { 53 | let lc = try LineCollection(for: url) 54 | 55 | // All should be valid 56 | XCTAssertEqual(try lc.position(for: 0), Position(line: 0, character: 0)) 57 | XCTAssertEqual(try lc.position(for: 49), Position(line: 5, character: 0)) 58 | XCTAssertEqual(try lc.position(for: 63), Position(line: 5, character: 14)) 59 | XCTAssertEqual(try lc.position(for: 123), Position(line: 11, character: 8)) 60 | XCTAssertEqual(try lc.position(for: 153), Position(line: 13, character: 1)) 61 | 62 | // All should be out of range 63 | XCTAssertNil(try? lc.position(for: -100)) 64 | XCTAssertNil(try? lc.position(for: 4700)) 65 | } catch { 66 | XCTFail(error.localizedDescription) 67 | } 68 | } 69 | 70 | func testSourceWithoutTrailingNewLine() { 71 | let url = getFixture("main.swift", in: "ValidLayouts/Simple/Sources")! 72 | 73 | do { 74 | let lc = try LineCollection(for: url) 75 | XCTAssertEqual(lc.lines.count, 2) 76 | } catch { 77 | XCTFail(error.localizedDescription) 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/SwiftModuleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftModuleTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 12/23/16. 6 | // 7 | // 8 | 9 | import Argo 10 | @testable import LanguageServerProtocol 11 | import XCTest 12 | 13 | class SwiftModuleTests: XCTestCase { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/SwiftSourceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftSourceTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | @testable import LanguageServerProtocol 11 | import XCTest 12 | 13 | class SwiftSourceTests: XCTestCase { 14 | 15 | func testFindDefintion() { 16 | let m = URL(fileURLWithPath: "/Users/ryan/Desktop/Test/Sources/main.swift") 17 | let b = URL(fileURLWithPath: "/Users/ryan/Desktop/Test/Sources/bar.swift") 18 | let ms = TextDocument(m) 19 | let bs = TextDocument(b) 20 | // let e = ms!.defines("s:vV4main3Bar1ySS") 21 | // XCTAssertNotNil(e) 22 | // print(e!) 23 | } 24 | 25 | func testTwo() { 26 | let m = URL(fileURLWithPath: "/Users/ryan/Desktop/Test/Sources/main.swift") 27 | let b = URL(fileURLWithPath: "/Users/ryan/Desktop/Test/Sources/bar.swift") 28 | let ms = TextDocument(m) 29 | let bs = TextDocument(b) 30 | // XCTAssertNil(ms!.defines("s:V4main3Bar")) 31 | // let e = bs!.defines("s:V4main3Bar") 32 | // XCTAssertNotNil(e) 33 | // print(e!) 34 | // print(e!) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Tests/LanguageServerProtocolTests/Types/WorkspaceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceTests.swift 3 | // langserver-swift 4 | // 5 | // Created by Ryan Lovelett on 11/21/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | @testable import LanguageServerProtocol 11 | import XCTest 12 | 13 | class WorkspaceTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testWorkspace() { 26 | // let workspaceRoot = getFixture(subdirectory: "ValidLayouts/Simple") 27 | // let w = Workspace(inDirectory: workspaceRoot) 28 | // let uri = TextDocumentIdentifier(uri: "Sources/main.swift") 29 | // let pos = Position(line: 0, character: 8) 30 | // let p = TextDocumentPositionParams(textDocument: uri, position: pos) 31 | // switch w.findDeclaration(forText: p) { 32 | // case .success(let x): 33 | // print(x) 34 | // case .error: 35 | // XCTFail("Could not find USR.") 36 | // } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 0.13.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | // 5 | // LinuxMain.swift 6 | // Tests 7 | // 8 | // Created by Sourcery on 2018-05-27T21:24:16-0400. 9 | // sourcery --sources Tests --templates sourcery --output Tests --args testimports='@testable import BaseProtocolTests 10 | // @testable import LanguageServerProtocolTests' 11 | // 12 | 13 | import XCTest 14 | @testable import BaseProtocolTests 15 | @testable import LanguageServerProtocolTests 16 | 17 | extension CompletionItemTests { 18 | static var allTests: [(String, (CompletionItemTests) -> () throws -> Void)] = [ 19 | ("testParseCompletionItems", testParseCompletionItems), 20 | ("testKeywordCompletionItem", testKeywordCompletionItem), 21 | ("testProtocolCompletionItem", testProtocolCompletionItem), 22 | ("testConstructorCompletionItem", testConstructorCompletionItem) 23 | ] 24 | } 25 | extension ConvertTests { 26 | static var allTests: [(String, (ConvertTests) -> () throws -> Void)] = [ 27 | ("testExample", testExample) 28 | ] 29 | } 30 | extension CursorTests { 31 | static var allTests: [(String, (CursorTests) -> () throws -> Void)] = [ 32 | ("testSystemSymbol", testSystemSymbol), 33 | ("testModuleSymbol", testModuleSymbol) 34 | ] 35 | } 36 | extension DidChangeWatchedFilesParamsTests { 37 | static var allTests: [(String, (DidChangeWatchedFilesParamsTests) -> () throws -> Void)] = [ 38 | ("testCreatedFile", testCreatedFile) 39 | ] 40 | } 41 | extension HeaderTests { 42 | static var allTests: [(String, (HeaderTests) -> () throws -> Void)] = [ 43 | ("testHeaderWithNumericContentLength", testHeaderWithNumericContentLength), 44 | ("testHeaderWithoutContentLength", testHeaderWithoutContentLength), 45 | ("testHeaderWithMultipleFields", testHeaderWithMultipleFields) 46 | ] 47 | } 48 | extension LineCollectionTests { 49 | static var allTests: [(String, (LineCollectionTests) -> () throws -> Void)] = [ 50 | ("testOffsetCalculation", testOffsetCalculation), 51 | ("testPositionCalculation", testPositionCalculation), 52 | ("testSourceWithoutTrailingNewLine", testSourceWithoutTrailingNewLine) 53 | ] 54 | } 55 | extension RequestIteratorTests { 56 | static var allTests: [(String, (RequestIteratorTests) -> () throws -> Void)] = [ 57 | ("testIteratingMultipleRequestsAndHeaders", testIteratingMultipleRequestsAndHeaders), 58 | ("testIteratingFullAndPartialRequestsWithoutCrash", testIteratingFullAndPartialRequestsWithoutCrash), 59 | ("testIteratingByAppendingBuffers", testIteratingByAppendingBuffers) 60 | ] 61 | } 62 | extension RequestTests { 63 | static var allTests: [(String, (RequestTests) -> () throws -> Void)] = [ 64 | ("testNotJSONContent", testNotJSONContent), 65 | ("testInvalidRequestJSON", testInvalidRequestJSON), 66 | ("testValidRequest", testValidRequest), 67 | ("testInvalidHeader", testInvalidHeader), 68 | ("testValidHeaderAndRequest", testValidHeaderAndRequest), 69 | ("testParsingParameters", testParsingParameters), 70 | ("testParsingIncorrectParameters", testParsingIncorrectParameters), 71 | ("testShutdownRequest", testShutdownRequest), 72 | ("testTextDocumentDidOpen", testTextDocumentDidOpen) 73 | ] 74 | } 75 | extension ResponseTests { 76 | static var allTests: [(String, (ResponseTests) -> () throws -> Void)] = [ 77 | ("testSendingErrorMessage", testSendingErrorMessage) 78 | ] 79 | } 80 | extension SwiftModuleTests { 81 | static var allTests: [(String, (SwiftModuleTests) -> () throws -> Void)] = [ 82 | ] 83 | } 84 | extension SwiftSourceTests { 85 | static var allTests: [(String, (SwiftSourceTests) -> () throws -> Void)] = [ 86 | ("testFindDefintion", testFindDefintion), 87 | ("testTwo", testTwo) 88 | ] 89 | } 90 | extension URLTests { 91 | static var allTests: [(String, (URLTests) -> () throws -> Void)] = [ 92 | ("testDecodeDirectory", testDecodeDirectory), 93 | ("testDecodeFile", testDecodeFile) 94 | ] 95 | } 96 | extension WorkspaceTests { 97 | static var allTests: [(String, (WorkspaceTests) -> () throws -> Void)] = [ 98 | ("testWorkspace", testWorkspace) 99 | ] 100 | } 101 | 102 | // swiftlint:disable trailing_comma 103 | XCTMain([ 104 | testCase(CompletionItemTests.allTests), 105 | testCase(ConvertTests.allTests), 106 | testCase(CursorTests.allTests), 107 | testCase(DidChangeWatchedFilesParamsTests.allTests), 108 | testCase(HeaderTests.allTests), 109 | testCase(LineCollectionTests.allTests), 110 | testCase(RequestIteratorTests.allTests), 111 | testCase(RequestTests.allTests), 112 | testCase(ResponseTests.allTests), 113 | testCase(SwiftModuleTests.allTests), 114 | testCase(SwiftSourceTests.allTests), 115 | testCase(URLTests.allTests), 116 | testCase(WorkspaceTests.allTests), 117 | ]) 118 | // swiftlint:enable trailing_comma 119 | -------------------------------------------------------------------------------- /settings.xcconfig: -------------------------------------------------------------------------------- 1 | MACOSX_DEPLOYMENT_TARGET = 10.12 2 | -------------------------------------------------------------------------------- /sourcery/LinuxMain.stencil: -------------------------------------------------------------------------------- 1 | // sourcery:file:LinuxMain.swift 2 | // 3 | // LinuxMain.swift 4 | // Tests 5 | // 6 | // Created by Sourcery on {% now "yyyy-MM-dd'T'HH:mm:ssZ" %}. 7 | // sourcery --sources Tests --templates sourcery --output Tests --args testimports='@testable import BaseProtocolTests 8 | // @testable import LanguageServerProtocolTests' 9 | // 10 | 11 | import XCTest 12 | {{ argument.testimports }} 13 | 14 | {% for type in types.classes|based:"XCTestCase" %} 15 | {% if not type.annotations.disableTests %}extension {{ type.name }} { 16 | static var allTests: [(String, ({{ type.name }}) -> () throws -> Void)] = [ 17 | {% for method in type.methods %}{% if method.parameters.count == 0 and method.shortName|hasPrefix:"test" %} ("{{ method.shortName }}", {{ method.shortName }}){% if not forloop.last %},{% endif %} 18 | {% endif %}{% endfor %}] 19 | } 20 | {% endif %}{% endfor %} 21 | 22 | // swiftlint:disable trailing_comma 23 | XCTMain([ 24 | {% for type in types.classes|based:"XCTestCase" %}{% if not type.annotations.disableTests %} testCase({{ type.name }}.allTests), 25 | {% endif %}{% endfor %}]) 26 | // swiftlint:enable trailing_comma 27 | // sourcery:end 28 | --------------------------------------------------------------------------------