sammccall created this revision. sammccall added a reviewer: kadircet. Herald added subscribers: cfe-commits, usaxena95, arphaman, jkorous, MaskRay, ilya-biryukov. Herald added a project: clang.
We support sending fragments in initialize and workspace/didChangeConfiguration. Sadly no workspace/configuration yet, we'd want to send that request on didOpen and we'd have to block on getting the answer, which is much more invasive. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D83822 Files: clang-tools-extra/clangd/ClangdLSPServer.cpp clang-tools-extra/clangd/ClangdLSPServer.h clang-tools-extra/clangd/ConfigFragment.h clang-tools-extra/clangd/ConfigYAML.cpp clang-tools-extra/clangd/Protocol.cpp clang-tools-extra/clangd/Protocol.h clang-tools-extra/clangd/test/config.test clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
Index: clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp +++ clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp @@ -119,6 +119,25 @@ ASSERT_THAT(Results, IsEmpty()); } +TEST(ParseYAML, JSON) { + CapturedDiags Diags; + llvm::json::Object Config{ + {"If", llvm::json::Object{{"UnknownCondition", "foo"}}}, + {"CompileFlags", llvm::json::Object{{"Add", "first"}}}, + }; + auto Result = + Fragment::parseJSON(std::move(Config), "config.json", Diags.callback()); + + ASSERT_THAT(Diags.Diagnostics, + ElementsAre(AllOf(DiagMessage("Unknown If key UnknownCondition"), + DiagKind(llvm::SourceMgr::DK_Warning)))); + EXPECT_EQ(Diags.Diagnostics.front().Rng, llvm::None); + + ASSERT_NE(Result, llvm::None); + EXPECT_TRUE(Result->If.HasUnrecognizedCondition); + EXPECT_THAT(Result->CompileFlags.Add, ElementsAre(Val("first"))); +} + } // namespace } // namespace config } // namespace clangd Index: clang-tools-extra/clangd/test/config.test =================================================================== --- /dev/null +++ clang-tools-extra/clangd/test/config.test @@ -0,0 +1,65 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# Set some config fragments on startup. +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","initializationOptions": { + "fragments": { + "!global": { + "CompileFlags": {"Add": "-DFOO=A"} + }, + "bar": { + "If": {"PathMatch":".*/bar.c"}, + "CompileFlags": {"Add": "-DFOO=B"} + } + } +}}} +--- +# foo.c gets the global configuration. +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///foo.c","languageId":"c", + "text":"int main() { return FOO; }" +}}} +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "code": "undeclared_var_use", +# CHECK-NEXT: "message": "Use of undeclared identifier 'A'", +# CHECK: } +# CHECK: ], +# CHECK-NEXT: "uri": "file://{{.*}}/foo.c", +# CHECK-NEXT: "version": 0 +# CHECK-NEXT: } +--- +# bar.c (also) gets the conditional configuration. +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///bar.c","languageId":"c", + "text":"int main() { return FOO; }" +}}} +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-NEXT: "params": { +# CHECK-NEXT: "diagnostics": [ +# CHECK-NEXT: { +# CHECK-NEXT: "code": "undeclared_var_use", +# CHECK-NEXT: "message": "Use of undeclared identifier 'B'", +# CHECK: } +# CHECK: ], +# CHECK-NEXT: "uri": "file://{{.*}}/bar.c", +# CHECK-NEXT: "version": 0 +# CHECK-NEXT: } +--- +# Replace global configuration. +{"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{ + "fragments": { + "!global": { + "CompileFlags": {"Add": "-DFOO=C"} + } + } +}}} +# Both files get rebuilt, and use the new config. +# CHECK: "method": "textDocument/publishDiagnostics", +# CHECK-DAG: "message": "Use of undeclared identifier 'C'", +# CHECK-DAG: "message": "Use of undeclared identifier 'B'", +--- +{"jsonrpc":"2.0","id":5,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} + + + Index: clang-tools-extra/clangd/Protocol.h =================================================================== --- clang-tools-extra/clangd/Protocol.h +++ clang-tools-extra/clangd/Protocol.h @@ -488,6 +488,11 @@ // Changes to the in-memory compilation database. // The key of the map is a file name. std::map<std::string, ClangdCompileCommand> compilationDatabaseChanges; + + // clangd configuration fragments: see https://clangd.llvm.org/config.html. + // Each value is an Object or null, and replaces any fragment with that key. + // Fragments are used in key order ("!foo" is low-priority, "~foo" is high). + std::map<std::string, llvm::json::Value> fragments; }; bool fromJSON(const llvm::json::Value &, ConfigurationSettings &); @@ -527,9 +532,6 @@ /// `rootUri` wins. llvm::Optional<URIForFile> rootUri; - // User provided initialization options. - // initializationOptions?: any; - /// The capabilities provided by the client (editor or tool) ClientCapabilities capabilities; @@ -537,6 +539,7 @@ llvm::Optional<TraceLevel> trace; /// User-provided initialization options. + /// LSP defines this type as `any`. InitializationOptions initializationOptions; }; bool fromJSON(const llvm::json::Value &, InitializeParams &); Index: clang-tools-extra/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/clangd/Protocol.cpp +++ clang-tools-extra/clangd/Protocol.cpp @@ -1077,6 +1077,9 @@ if (!O) return true; // 'any' type in LSP. O.map("compilationDatabaseChanges", S.compilationDatabaseChanges); + if (auto *Clangd = Params.getAsObject()->getObject("fragments")) + for (const auto &Entry : *Clangd) + S.fragments.emplace(Entry.first.str(), llvm::json::Value(Entry.second)); return true; } Index: clang-tools-extra/clangd/ConfigYAML.cpp =================================================================== --- clang-tools-extra/clangd/ConfigYAML.cpp +++ clang-tools-extra/clangd/ConfigYAML.cpp @@ -11,6 +11,7 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/YAMLParser.h" #include <system_error> @@ -225,6 +226,22 @@ return Result; } +llvm::Optional<Fragment> Fragment::parseJSON(const llvm::json::Value &Value, + llvm::StringRef Source, + DiagnosticCallback Diags) { + // This is a horrible hack: JSON is a subset of YAML, so use the YAML parser. + // FIXME: The diagnostics produced will use YAML terms etc. Do this properly. + auto NoLocDiags = [&](const llvm::SMDiagnostic &D) { + Diags(llvm::SMDiagnostic(Source, D.getKind(), D.getMessage())); + }; + auto Result = parseYAML(llvm::to_string(Value), "", NoLocDiags); + + assert(Result.size() <= 1 && "JSON yielded multiple YAML documents?"); + if (Result.empty()) + return llvm::None; + return std::move(Result.front()); +} + } // namespace config } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/ConfigFragment.h =================================================================== --- clang-tools-extra/clangd/ConfigFragment.h +++ clang-tools-extra/clangd/ConfigFragment.h @@ -36,6 +36,7 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/SMLoc.h" #include "llvm/Support/SourceMgr.h" #include <string> @@ -69,6 +70,13 @@ llvm::StringRef BufferName, DiagnosticCallback); + /// Parses a fragment from a JSON value (an Object). + /// Returns None if the value contained a fatal error and could not be parsed. + /// Locations are not reported with the diagnostics. + static llvm::Optional<Fragment> parseJSON(const llvm::json::Value &JSON, + llvm::StringRef SourceName, + DiagnosticCallback); + /// Analyzes and consumes this fragment, possibly yielding more diagnostics. /// This always produces a usable result (errors are recovered). /// Index: clang-tools-extra/clangd/ClangdLSPServer.h =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.h +++ clang-tools-extra/clangd/ClangdLSPServer.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CLANGDLSPSERVER_H #include "ClangdServer.h" +#include "ConfigProvider.h" #include "DraftStore.h" #include "Features.inc" #include "FindSymbols.h" @@ -209,6 +210,22 @@ notify("$/progress", Params); } + /// Stores clangd config set over LSP. + class LSPConfigProvider : public config::Provider { + mutable std::mutex Mu; + mutable std::map<std::string, config::CompiledFragment> Cached; + llvm::StringMap<llvm::json::Value> Pending; + + std::vector<config::CompiledFragment> + getFragments(const config::Params &, + config::DiagnosticCallback) const override; + + public: + void update(const std::map<std::string, llvm::json::Value> &); + }; + LSPConfigProvider LSPConfig; + std::unique_ptr<config::Provider> ConfigProviderStorage; + const ThreadsafeFS &TFS; /// Options used for code completion clangd::CodeCompleteOptions CCOpts; Index: clang-tools-extra/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -8,6 +8,7 @@ #include "ClangdLSPServer.h" #include "CodeComplete.h" +#include "ConfigFragment.h" #include "Diagnostics.h" #include "DraftStore.h" #include "GlobalCompilationDatabase.h" @@ -1199,6 +1200,12 @@ void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { + bool ReparseAllFiles = false; + if (!Settings.fragments.empty()) { + LSPConfig.update(Settings.fragments); + ReparseAllFiles = true; // Config may affect any open file. + } + // Per-file update to the compilation database. llvm::StringSet<> ModifiedFiles; for (auto &Entry : Settings.compilationDatabaseChanges) { @@ -1214,8 +1221,9 @@ } } - reparseOpenFilesIfNeeded( - [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; }); + reparseOpenFilesIfNeeded([&](llvm::StringRef File) { + return ReparseAllFiles || ModifiedFiles.count(File) != 0; + }); } void ClangdLSPServer::publishTheiaSemanticHighlighting( @@ -1234,6 +1242,35 @@ applyConfiguration(Params.settings); } +std::vector<config::CompiledFragment> +ClangdLSPServer::LSPConfigProvider::getFragments( + const config::Params &P, config::DiagnosticCallback DC) const { + std::lock_guard<std::mutex> Lock(Mu); + // Compile any pending configs, and merge them into the cache. + for (const auto &Entry : Pending) { + llvm::Optional<config::Fragment> Fragment; + if (!Entry.second.getAsNull()) + Fragment = config::Fragment::parseJSON( + Entry.second, ("LSP:" + Entry.first()).str(), DC); + if (Fragment) + Cached[Entry.first().str()] = std::move(*Fragment).compile(DC); + else + Cached.erase(Entry.first().str()); + } + // Now extract all fragments from the cache. + std::vector<config::CompiledFragment> Results; + for (const auto &Entry : Cached) // Sorted by key, as desired. + Results.push_back(Entry.second); + return Results; +} + +void ClangdLSPServer::LSPConfigProvider::update( + const std::map<std::string, llvm::json::Value> &M) { + std::lock_guard<std::mutex> Lock(Mu); + for (const auto &Entry : M) + Pending.insert_or_assign(Entry.first, Entry.second); +} + void ClangdLSPServer::onReference(const ReferenceParams &Params, Callback<std::vector<Location>> Reply) { Server->findReferences(Params.textDocument.uri.file(), Params.position, @@ -1405,6 +1442,13 @@ if (Opts.FoldingRanges) MsgHandler->bind("textDocument/foldingRange", &ClangdLSPServer::onFoldingRange); // clang-format on + if (Opts.ConfigProvider) { + ConfigProviderStorage = + config::Provider::combine({&LSPConfig, Opts.ConfigProvider}); + ClangdServerOpts.ConfigProvider = ConfigProviderStorage.get(); + } else { + ClangdServerOpts.ConfigProvider = &LSPConfig; + } } ClangdLSPServer::~ClangdLSPServer() {
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits