From 37de9718d062fd866a6dc7787f14869692b882dd Mon Sep 17 00:00:00 2001 From: Marc-Andre Laperle Date: Wed, 27 Sep 2017 15:31:17 +0000 Subject: [PATCH] [clangd] Handle InitializeParams and store rootUri Summary: The root Uri is the workspace location and will be useful in the context of indexing. We could also add more things to InitializeParams in order to configure Clangd for C/C++ sepecific extensions. Reviewers: ilya-biryukov, bkramer, krasimir, Nebiroth Reviewed By: ilya-biryukov Subscribers: ilya-biryukov Tags: #clang-tools-extra Differential Revision: https://reviews.llvm.org/D38093 llvm-svn: 314309 --- clang-tools-extra/clangd/ClangdLSPServer.cpp | 8 ++- clang-tools-extra/clangd/ClangdServer.cpp | 12 ++++- clang-tools-extra/clangd/ClangdServer.h | 5 ++ clang-tools-extra/clangd/Protocol.cpp | 57 ++++++++++++++++++++++ clang-tools-extra/clangd/Protocol.h | 37 ++++++++++++++ clang-tools-extra/clangd/ProtocolHandlers.cpp | 8 ++- clang-tools-extra/clangd/ProtocolHandlers.h | 3 +- .../test/clangd/initialize-params-invalid.test | 21 ++++++++ .../test/clangd/initialize-params.test | 21 ++++++++ 9 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 clang-tools-extra/test/clangd/initialize-params-invalid.test create mode 100644 clang-tools-extra/test/clangd/initialize-params.test diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index b02891b..e87c9fd 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -51,7 +51,8 @@ class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks { public: LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {} - void onInitialize(StringRef ID, JSONOutput &Out) override; + void onInitialize(StringRef ID, InitializeParams IP, + JSONOutput &Out) override; void onShutdown(JSONOutput &Out) override; void onDocumentDidOpen(DidOpenTextDocumentParams Params, JSONOutput &Out) override; @@ -77,6 +78,7 @@ private: }; void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID, + InitializeParams IP, JSONOutput &Out) { Out.writeMessage( R"({"jsonrpc":"2.0","id":)" + ID + @@ -89,6 +91,10 @@ void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID, "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]}, "definitionProvider": true }}})"); + if (IP.rootUri && !IP.rootUri->file.empty()) + LangServer.Server.setRootPath(IP.rootUri->file); + else if (IP.rootPath && !IP.rootPath->empty()) + LangServer.Server.setRootPath(*IP.rootPath); } void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) { diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 347f53c..def0bca 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -15,6 +15,7 @@ #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include @@ -154,12 +155,19 @@ ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB, SnippetCompletions(SnippetCompletions), WorkScheduler(AsyncThreadsCount) { } +void ClangdServer::setRootPath(PathRef RootPath) { + std::string NewRootPath = llvm::sys::path::convert_to_slash( + RootPath, llvm::sys::path::Style::posix); + if (llvm::sys::fs::is_directory(NewRootPath)) + this->RootPath = NewRootPath; +} + std::future ClangdServer::addDocument(PathRef File, StringRef Contents) { DocVersion Version = DraftMgr.updateDraft(File, Contents); auto TaggedFS = FSProvider.getTaggedFileSystem(File); - std::shared_ptr Resources = - Units.getOrCreateFile(File, ResourceDir, CDB, PCHs, TaggedFS.Value, Logger); + std::shared_ptr Resources = Units.getOrCreateFile( + File, ResourceDir, CDB, PCHs, TaggedFS.Value, Logger); return scheduleReparseAndDiags(File, VersionedDraft{Version, Contents.str()}, std::move(Resources), std::move(TaggedFS)); } diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 63c5458..3e202f5 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -211,6 +211,9 @@ public: bool SnippetCompletions, clangd::Logger &Logger, llvm::Optional ResourceDir = llvm::None); + /// Set the root path of the workspace. + void setRootPath(PathRef RootPath); + /// Add a \p File to the list of tracked C++ files or update the contents if /// \p File is already tracked. Also schedules parsing of the AST for it on a /// separate thread. When the parsing is complete, DiagConsumer passed in @@ -278,6 +281,8 @@ private: DraftStore DraftMgr; CppFileCollection Units; std::string ResourceDir; + // If set, this represents the workspace path. + llvm::Optional RootPath; std::shared_ptr PCHs; bool SnippetCompletions; /// Used to serialize diagnostic callbacks. diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index d9da386..e68add6 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -286,6 +286,63 @@ std::string TextEdit::unparse(const TextEdit &P) { return Result; } +namespace { +TraceLevel getTraceLevel(llvm::StringRef TraceLevelStr, + clangd::Logger &Logger) { + if (TraceLevelStr == "off") + return TraceLevel::Off; + else if (TraceLevelStr == "messages") + return TraceLevel::Messages; + else if (TraceLevelStr == "verbose") + return TraceLevel::Verbose; + + Logger.log(llvm::formatv("Unknown trace level \"{0}\"\n", TraceLevelStr)); + return TraceLevel::Off; +} +} // namespace + +llvm::Optional +InitializeParams::parse(llvm::yaml::MappingNode *Params, + clangd::Logger &Logger) { + InitializeParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + continue; + + if (KeyValue == "processId") { + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + long long Val; + if (llvm::getAsSignedInteger(Value->getValue(KeyStorage), 0, Val)) + return llvm::None; + Result.processId = Val; + } else if (KeyValue == "rootPath") { + Result.rootPath = Value->getValue(KeyStorage); + } else if (KeyValue == "rootUri") { + Result.rootUri = URI::parse(Value); + } else if (KeyValue == "initializationOptions") { + // Not used + } else if (KeyValue == "capabilities") { + // Not used + } else if (KeyValue == "trace") { + Result.trace = getTraceLevel(Value->getValue(KeyStorage), Logger); + } else { + logIgnoredField(KeyValue, Logger); + } + } + return Result; +} + llvm::Optional DidOpenTextDocumentParams::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 3c6330e..71ab957 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -160,6 +160,43 @@ struct TextDocumentItem { clangd::Logger &Logger); }; +enum class TraceLevel { + Off = 0, + Messages = 1, + Verbose = 2, +}; + +struct InitializeParams { + /// The process Id of the parent process that started + /// the server. Is null if the process has not been started by another + /// process. If the parent process is not alive then the server should exit + /// (see exit notification) its process. + llvm::Optional processId; + + /// The rootPath of the workspace. Is null + /// if no folder is open. + /// + /// @deprecated in favour of rootUri. + llvm::Optional rootPath; + + /// The rootUri of the workspace. Is null if no + /// folder is open. If both `rootPath` and `rootUri` are set + /// `rootUri` wins. + llvm::Optional rootUri; + + // User provided initialization options. + // initializationOptions?: any; + + /// The capabilities provided by the client (editor or tool) + /// Note: Not currently used by clangd + // ClientCapabilities capabilities; + + /// The initial trace setting. If omitted trace is disabled ('off'). + llvm::Optional trace; + static llvm::Optional parse(llvm::yaml::MappingNode *Params, + clangd::Logger &Logger); +}; + struct DidOpenTextDocumentParams { /// The document that was opened. TextDocumentItem textDocument; diff --git a/clang-tools-extra/clangd/ProtocolHandlers.cpp b/clang-tools-extra/clangd/ProtocolHandlers.cpp index 6d527ef0..06e07db 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.cpp +++ b/clang-tools-extra/clangd/ProtocolHandlers.cpp @@ -21,7 +21,13 @@ struct InitializeHandler : Handler { : Handler(Output), Callbacks(Callbacks) {} void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override { - Callbacks.onInitialize(ID, Output); + auto IP = InitializeParams::parse(Params, Output); + if (!IP) { + Output.log("Failed to decode InitializeParams!\n"); + IP = InitializeParams(); + } + + Callbacks.onInitialize(ID, *IP, Output); } private: diff --git a/clang-tools-extra/clangd/ProtocolHandlers.h b/clang-tools-extra/clangd/ProtocolHandlers.h index 2341ebf..d2b2f18 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.h +++ b/clang-tools-extra/clangd/ProtocolHandlers.h @@ -27,7 +27,8 @@ class ProtocolCallbacks { public: virtual ~ProtocolCallbacks() = default; - virtual void onInitialize(StringRef ID, JSONOutput &Out) = 0; + virtual void onInitialize(StringRef ID, InitializeParams IP, + JSONOutput &Out) = 0; virtual void onShutdown(JSONOutput &Out) = 0; virtual void onDocumentDidOpen(DidOpenTextDocumentParams Params, JSONOutput &Out) = 0; diff --git a/clang-tools-extra/test/clangd/initialize-params-invalid.test b/clang-tools-extra/test/clangd/initialize-params-invalid.test new file mode 100644 index 0000000..9563c9e --- /dev/null +++ b/clang-tools-extra/test/clangd/initialize-params-invalid.test @@ -0,0 +1,21 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %s +# It is absolutely vital that this file has CRLF line endings. +# +# Test with invalid initialize request parameters +Content-Length: 142 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","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: }}} +# +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} diff --git a/clang-tools-extra/test/clangd/initialize-params.test b/clang-tools-extra/test/clangd/initialize-params.test new file mode 100644 index 0000000..1277095 --- /dev/null +++ b/clang-tools-extra/test/clangd/initialize-params.test @@ -0,0 +1,21 @@ +# RUN: clangd -run-synchronously < %s | FileCheck %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: }}} +# +Content-Length: 44 + +{"jsonrpc":"2.0","id":3,"method":"shutdown"} -- 2.7.4