[clang-tools-extra] r314693 - [clangd] Handle workspace/didChangeWatchedFiles

Marc-Andre Laperle via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 2 11:00:37 PDT 2017


Author: malaperle
Date: Mon Oct  2 11:00:37 2017
New Revision: 314693

URL: http://llvm.org/viewvc/llvm-project?rev=314693&view=rev
Log:
[clangd] Handle workspace/didChangeWatchedFiles

Summary:
The client can send notifications when it detects watched files have
changed. This patch adds the protocol handling for this type of notification.
For now, the notification will be passed down to the ClangdServer, but it will
not be acted upon. However, this will become useful for the indexer to react
to file changes.
The events could also potentially be used to invalidate other caches
(compilation database, etc).

This change also updates the VSCode extension so that it sends the events.

Signed-off-by: Marc-Andre Laperle <marc-andre.laperle at ericsson.com>

Reviewers: ilya-biryukov, Nebiroth

Subscribers: ilya-biryukov

Tags: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D38422

Added:
    clang-tools-extra/trunk/test/clangd/did-change-watch-files.test
Modified:
    clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
    clang-tools-extra/trunk/clangd/ClangdLSPServer.h
    clang-tools-extra/trunk/clangd/ClangdServer.cpp
    clang-tools-extra/trunk/clangd/ClangdServer.h
    clang-tools-extra/trunk/clangd/Protocol.cpp
    clang-tools-extra/trunk/clangd/Protocol.h
    clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
    clang-tools-extra/trunk/clangd/ProtocolHandlers.h
    clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json
    clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Mon Oct  2 11:00:37 2017
@@ -73,6 +73,10 @@ void ClangdLSPServer::onDocumentDidChang
                      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);

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.h?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h Mon Oct  2 11:00:37 2017
@@ -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);

Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Mon Oct  2 11:00:37 2017
@@ -424,3 +424,8 @@ ClangdServer::scheduleCancelRebuild(std:
                            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.
+}

Modified: clang-tools-extra/trunk/clangd/ClangdServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.h Mon Oct  2 11:00:37 2017
@@ -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>

Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Mon Oct  2 11:00:37 2017
@@ -447,6 +447,84 @@ DidChangeTextDocumentParams::parse(llvm:
   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) {

Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Mon Oct  2 11:00:37 2017
@@ -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;

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Mon Oct  2 11:00:37 2017
@@ -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(JS
       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));
 }

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.h?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h Mon Oct  2 11:00:37 2017
@@ -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,

Modified: clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json (original)
+++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/package.json Mon Oct  2 11:00:37 2017
@@ -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."
                 }
             }
         }

Modified: clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts?rev=314693&r1=314692&r2=314693&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts (original)
+++ clang-tools-extra/trunk/clangd/clients/clangd-vscode/src/extension.ts Mon Oct  2 11:00:37 2017
@@ -18,18 +18,24 @@ function getConfig<T>(option: string, de
 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 + '}')
         }
     };
 

Added: clang-tools-extra/trunk/test/clangd/did-change-watch-files.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/did-change-watch-files.test?rev=314693&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clangd/did-change-watch-files.test (added)
+++ clang-tools-extra/trunk/test/clangd/did-change-watch-files.test Mon Oct  2 11:00:37 2017
@@ -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"}




More information about the cfe-commits mailing list