Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -51,7 +51,8 @@ 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 @@ }; void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID, + InitializeParams IP, JSONOutput &Out) { Out.writeMessage( R"({"jsonrpc":"2.0","id":)" + ID + @@ -89,6 +91,10 @@ "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) { Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -211,6 +211,9 @@ 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 @@ 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. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ 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 @@ 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)); } Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -160,6 +160,43 @@ 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; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -286,6 +286,63 @@ 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) { Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -27,7 +27,8 @@ 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; Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -21,7 +21,13 @@ : 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: Index: test/clangd/initialize-params-invalid.test =================================================================== --- /dev/null +++ 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"} Index: test/clangd/initialize-params.test =================================================================== --- /dev/null +++ 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"}