diff options
| -rw-r--r-- | clang-tools-extra/clangd/ClangdLSPServer.cpp | 4 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ClangdLSPServer.h | 1 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ClangdServer.cpp | 5 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ClangdServer.h | 2 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/Protocol.cpp | 78 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/Protocol.h | 27 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ProtocolHandlers.cpp | 24 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/ProtocolHandlers.h | 1 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/clients/clangd-vscode/package.json | 5 | ||||
| -rw-r--r-- | clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts | 8 | ||||
| -rw-r--r-- | clang-tools-extra/test/clangd/did-change-watch-files.test | 61 |
11 files changed, 214 insertions, 2 deletions
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 19a89353e0f..0384423e151 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -73,6 +73,10 @@ void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams Params, Params.contentChanges[0].text); } +void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { + Server.onFileEvent(Params); +} + void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams Params, JSONOutput &Out) { Server.removeDocument(Params.textDocument.uri.file); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 608ff9bf244..656252f868d 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -71,6 +71,7 @@ private: JSONOutput &Out) override; void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) override; + void onFileEvent(const DidChangeWatchedFilesParams &Params) override; std::vector<clang::tooling::Replacement> getFixIts(StringRef File, const clangd::Diagnostic &D); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 2cf2cb791eb..f5d03252422 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -424,3 +424,8 @@ ClangdServer::scheduleCancelRebuild(std::shared_ptr<CppFile> Resources) { std::move(DeferredCancel)); return DoneFuture; } + +void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { + // FIXME: Do nothing for now. This will be used for indexing and potentially + // invalidating other caches. +} diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index b819264e46a..b42d2d6226d 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -268,6 +268,8 @@ public: /// Waits until all requests to worker thread are finished and dumps AST for /// \p File. \p File must be in the list of added documents. std::string dumpAST(PathRef File); + /// Called when an event occurs for a watched file in the workspace. + void onFileEvent(const DidChangeWatchedFilesParams &Params); private: std::future<void> diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index e68add6e4e8..f45e07d17c1 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -447,6 +447,84 @@ DidChangeTextDocumentParams::parse(llvm::yaml::MappingNode *Params, return Result; } +llvm::Optional<FileEvent> FileEvent::parse(llvm::yaml::MappingNode *Params, + clangd::Logger &Logger) { + llvm::Optional<FileEvent> Result = FileEvent(); + for (auto &NextKeyValue : *Params) { + // We have to consume the whole MappingNode because it doesn't support + // skipping and we want to be able to parse further valid events. + if (!Result) + continue; + + auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey()); + if (!KeyString) { + Result.reset(); + continue; + } + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue()); + if (!Value) { + Result.reset(); + continue; + } + llvm::SmallString<10> Storage; + if (KeyValue == "uri") { + Result->uri = URI::parse(Value); + } else if (KeyValue == "type") { + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(Storage), 0, Val)) { + Result.reset(); + continue; + } + Result->type = static_cast<FileChangeType>(Val); + if (Result->type < FileChangeType::Created || + Result->type > FileChangeType::Deleted) + Result.reset(); + } else { + logIgnoredField(KeyValue, Logger); + } + } + return Result; +} + +llvm::Optional<DidChangeWatchedFilesParams> +DidChangeWatchedFilesParams::parse(llvm::yaml::MappingNode *Params, + clangd::Logger &Logger) { + DidChangeWatchedFilesParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = NextKeyValue.getValue(); + + llvm::SmallString<10> Storage; + if (KeyValue == "changes") { + auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value); + if (!Seq) + return llvm::None; + for (auto &Item : *Seq) { + auto *I = dyn_cast<llvm::yaml::MappingNode>(&Item); + if (!I) + return llvm::None; + auto Parsed = FileEvent::parse(I, Logger); + if (Parsed) + Result.changes.push_back(std::move(*Parsed)); + else + Logger.log("Failed to decode a FileEvent.\n"); + } + } else { + logIgnoredField(KeyValue, Logger); + } + } + return Result; +} + llvm::Optional<TextDocumentContentChangeEvent> TextDocumentContentChangeEvent::parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger) { diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 71ab9573a58..a6fcca6a40a 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -237,6 +237,33 @@ struct DidChangeTextDocumentParams { parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); }; +enum class FileChangeType { + /// The file got created. + Created = 1, + /// The file got changed. + Changed = 2, + /// The file got deleted. + Deleted = 3 +}; + +struct FileEvent { + /// The file's URI. + URI uri; + /// The change type. + FileChangeType type; + + static llvm::Optional<FileEvent> parse(llvm::yaml::MappingNode *Params, + clangd::Logger &Logger); +}; + +struct DidChangeWatchedFilesParams { + /// The actual file events. + std::vector<FileEvent> changes; + + static llvm::Optional<DidChangeWatchedFilesParams> + parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger); +}; + struct FormattingOptions { /// Size of a tab in spaces. int tabSize; diff --git a/clang-tools-extra/clangd/ProtocolHandlers.cpp b/clang-tools-extra/clangd/ProtocolHandlers.cpp index 544c44c6f23..936c673ebab 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.cpp +++ b/clang-tools-extra/clangd/ProtocolHandlers.cpp @@ -226,6 +226,25 @@ private: ProtocolCallbacks &Callbacks; }; +struct WorkspaceDidChangeWatchedFilesHandler : Handler { + WorkspaceDidChangeWatchedFilesHandler(JSONOutput &Output, + ProtocolCallbacks &Callbacks) + : Handler(Output), Callbacks(Callbacks) {} + + void handleNotification(llvm::yaml::MappingNode *Params) { + auto DCWFP = DidChangeWatchedFilesParams::parse(Params, Output); + if (!DCWFP) { + Output.log("Failed to decode DidChangeWatchedFilesParams.\n"); + return; + } + + Callbacks.onFileEvent(*DCWFP); + } + +private: + ProtocolCallbacks &Callbacks; +}; + } // namespace void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, @@ -264,5 +283,8 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, llvm::make_unique<GotoDefinitionHandler>(Out, Callbacks)); Dispatcher.registerHandler( "textDocument/switchSourceHeader", - llvm::make_unique<SwitchSourceHeaderHandler>(Out, Callbacks)); + llvm::make_unique<SwitchSourceHeaderHandler>(Out, Callbacks)); + Dispatcher.registerHandler( + "workspace/didChangeWatchedFiles", + llvm::make_unique<WorkspaceDidChangeWatchedFilesHandler>(Out, Callbacks)); } diff --git a/clang-tools-extra/clangd/ProtocolHandlers.h b/clang-tools-extra/clangd/ProtocolHandlers.h index 70796d34bf3..8592e574c8d 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.h +++ b/clang-tools-extra/clangd/ProtocolHandlers.h @@ -51,6 +51,7 @@ public: JSONOutput &Out) = 0; virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) = 0; + virtual void onFileEvent(const DidChangeWatchedFilesParams &Params) = 0; }; void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/package.json b/clang-tools-extra/clangd/clients/clangd-vscode/package.json index 729297af172..c048d965d3b 100644 --- a/clang-tools-extra/clangd/clients/clangd-vscode/package.json +++ b/clang-tools-extra/clangd/clients/clangd-vscode/package.json @@ -51,6 +51,11 @@ "type": "string" }, "description": "Arguments for clangd server" + }, + "clangd.syncFileEvents": { + "type": "boolean", + "default": true, + "description": "Whether or not to send file events to clangd (File created, changed or deleted). This can be disabled for performance consideration." } } } diff --git a/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts b/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts index c76e0a021ee..f89ddc97ffe 100644 --- a/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts +++ b/clang-tools-extra/clangd/clients/clangd-vscode/src/extension.ts @@ -18,18 +18,24 @@ function getConfig<T>(option: string, defaultValue?: any) : T { export function activate(context: vscode.ExtensionContext) { const clangdPath = getConfig<string>('path'); const clangdArgs = getConfig<string[]>('arguments'); + const syncFileEvents = getConfig<boolean>('syncFileEvents', true); const serverOptions: vscodelc.ServerOptions = { command: clangdPath, args: clangdArgs }; + const cppFileExtensions: string[] = ['cpp', 'c', 'cc', 'cxx', 'c++', 'm', 'mm', 'h', 'hh', 'hpp', 'hxx', 'inc']; + const cppFileExtensionsPattern = cppFileExtensions.join(); const clientOptions: vscodelc.LanguageClientOptions = { // Register the server for C/C++ files - documentSelector: ['c', 'cc', 'cpp', 'h', 'hh', 'hpp'], + documentSelector: cppFileExtensions, uriConverters: { // FIXME: by default the URI sent over the protocol will be percent encoded (see rfc3986#section-2.1) // the "workaround" below disables temporarily the encoding until decoding // is implemented properly in clangd code2Protocol: (uri: vscode.Uri) : string => uri.toString(true), protocol2Code: (uri: string) : vscode.Uri => vscode.Uri.parse(uri) + }, + synchronize: !syncFileEvents ? undefined : { + fileEvents: vscode.workspace.createFileSystemWatcher('**/*.{' + cppFileExtensionsPattern + '}') } }; diff --git a/clang-tools-extra/test/clangd/did-change-watch-files.test b/clang-tools-extra/test/clangd/did-change-watch-files.test new file mode 100644 index 00000000000..d9ef6b4c214 --- /dev/null +++ b/clang-tools-extra/test/clangd/did-change-watch-files.test @@ -0,0 +1,61 @@ +# RUN: clangd -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s
+# It is absolutely vital that this file has CRLF line endings.
+#
+# Test initialize request parameters with rootUri
+Content-Length: 143
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
+# CHECK: Content-Length: 466
+# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
+# CHECK: "textDocumentSync": 1,
+# CHECK: "documentFormattingProvider": true,
+# CHECK: "documentRangeFormattingProvider": true,
+# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
+# CHECK: "codeActionProvider": true,
+# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
+# CHECK: "definitionProvider": true
+# CHECK: }}}
+#
+#Normal case
+Content-Length: 217
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file.cpp","type":1},{"uri":"file:///path/to/file2.cpp","type":2},{"uri":"file:///path/to/file3.cpp","type":3}]}}
+
+# Wrong event type, integer
+Content-Length: 173
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":0},{"uri":"file:///path/to/file3.cpp","type":4}]}}
+# STDERR: Failed to decode a FileEvent.
+# Wrong event type, string
+Content-Length: 132
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":"foo"}]}}
+# STDERR: Failed to decode a FileEvent.
+#Custom event field
+Content-Length: 143
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":1,"custom":"foo"}]}}
+# STDERR: Failed to decode a FileEvent.
+#Event field with object
+Content-Length: 140
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file2.cpp","type":{"foo":"bar"}}]}}
+# STDERR: Failed to decode a FileEvent.
+# Changes field with sequence but no object
+Content-Length: 86
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[""]}}
+# STDERR: Failed to decode DidChangeWatchedFilesParams.
+# Changes field with no sequence
+Content-Length: 84
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":""}}
+# STDERR: Failed to decode DidChangeWatchedFilesParams.
+# Custom field
+Content-Length: 86
+
+{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"custom":"foo"}}
+# STDERR: Ignored unknown field "custom"
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
|

