diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -75,6 +75,7 @@ void onDocumentDidOpen(const DidOpenTextDocumentParams &); void onDocumentDidChange(const DidChangeTextDocumentParams &); void onDocumentDidClose(const DidCloseTextDocumentParams &); + void onDocumentDidSave(const DidSaveTextDocumentParams &); void onDocumentOnTypeFormatting(const DocumentOnTypeFormattingParams &, Callback>); void onDocumentRangeFormatting(const DocumentRangeFormattingParams &, @@ -131,10 +132,12 @@ /// produce '->' and '::', respectively. bool shouldRunCompletion(const CompletionParams &Params) const; - /// Forces a reparse of all currently opened files which were modified. As a - /// result, this method may be very expensive. This method is normally called - /// when the compilation database is changed. - void reparseOpenedFiles(const llvm::StringSet<> &ModifiedFiles); + /// Requests a reparse of currently opened files using their latest source. + /// This will typically only rebuild if something other than the source has + /// changed (e.g. the CDB yields different flags, or files included in the + /// preamble have been modified). + void reparseOpenFilesIfNeeded( + llvm::function_ref Filter); void applyConfiguration(const ConfigurationSettings &Settings); /// Sends a "publishSemanticHighlighting" notification to the LSP client. diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -569,7 +569,12 @@ {"version", getClangToolFullVersion("clangd")}}}, {"capabilities", llvm::json::Object{ - {"textDocumentSync", (int)TextDocumentSyncKind::Incremental}, + {"textDocumentSync", + llvm::json::Object{ + {"openClose", true}, + {"change", (int)TextDocumentSyncKind::Incremental}, + {"save", true}, + }}, {"documentFormattingProvider", true}, {"documentRangeFormattingProvider", true}, {"documentOnTypeFormattingProvider", @@ -684,7 +689,16 @@ WantDiags, Params.forceRebuild); } +void ClangdLSPServer::onDocumentDidSave( + const DidSaveTextDocumentParams &Params) { + reparseOpenFilesIfNeeded([](llvm::StringRef) { return true; }); +} + void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { + // We could also reparse all open files here. However: + // - this could be frequent, and revalidating all the preambles isn't free + // - this is useful e.g. when switching git branches, but we're likely to see + // fresh headers but still have the old-branch main-file content Server->onFileEvent(Params); } @@ -1180,7 +1194,8 @@ } } - reparseOpenedFiles(ModifiedFiles); + reparseOpenFilesIfNeeded( + [&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; }); } void ClangdLSPServer::publishTheiaSemanticHighlighting( @@ -1358,6 +1373,7 @@ MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen); MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose); MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); + MsgHandler->bind("textDocument/didSave", &ClangdLSPServer::onDocumentDidSave); MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); @@ -1566,13 +1582,11 @@ notify("textDocument/clangd.fileStatus", Status.render(File)); } -void ClangdLSPServer::reparseOpenedFiles( - const llvm::StringSet<> &ModifiedFiles) { - if (ModifiedFiles.empty()) - return; +void ClangdLSPServer::reparseOpenFilesIfNeeded( + llvm::function_ref Filter) { // Reparse only opened files that were modified. for (const Path &FilePath : DraftMgr.getActiveFiles()) - if (ModifiedFiles.find(FilePath) != ModifiedFiles.end()) + if (Filter(FilePath)) if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? Server->addDocument(FilePath, std::move(Draft->Contents), encodeVersion(Draft->Version), diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -654,6 +654,12 @@ }; bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &); +struct DidSaveTextDocumentParams { + /// The document that was saved. + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const llvm::json::Value &, DidSaveTextDocumentParams &); + struct TextDocumentContentChangeEvent { /// The range of the document that changed. llvm::Optional range; diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -452,6 +452,11 @@ return O && O.map("textDocument", R.textDocument); } +bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R) { + llvm::json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R) { llvm::json::ObjectMapper O(Params); if (!O) diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -56,7 +56,11 @@ # CHECK-NEXT: "," # CHECK-NEXT: ] # CHECK-NEXT: }, -# CHECK-NEXT: "textDocumentSync": 2, +# CHECK-NEXT: "textDocumentSync": { +# CHECK-NEXT: "change": 2, +# CHECK-NEXT: "openClose": true, +# CHECK-NEXT: "save": true +# CHECK-NEXT: }, # CHECK-NEXT: "typeHierarchyProvider": true # CHECK-NEXT: "workspaceSymbolProvider": true # CHECK-NEXT: }, diff --git a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp --- a/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp +++ b/clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp @@ -126,6 +126,27 @@ EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty())); } +TEST_F(LSPTest, DiagnosticsHeaderSaved) { + auto &Client = start(); + FS.Files["foo.h"] = "#define VAR original"; + Client.didOpen("foo.cpp", R"cpp( + #include "foo.h" + int x = VAR; + )cpp"); + EXPECT_THAT(Client.diagnostics("foo.cpp"), + llvm::ValueIs(testing::ElementsAre( + DiagMessage("Use of undeclared identifier 'original'")))); + // Now modify the header from within the "editor". + FS.Files["foo.h"] = "#define VAR changed"; + Client.notify( + "textDocument/didSave", + llvm::json::Object{{"textDocument", Client.documentID("foo.h")}}); + // Foo.cpp should be rebuilt with new diagnostics. + EXPECT_THAT(Client.diagnostics("foo.cpp"), + llvm::ValueIs(testing::ElementsAre( + DiagMessage("Use of undeclared identifier 'changed'")))); +} + } // namespace } // namespace clangd } // namespace clang