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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits