Skip to content

Commit 596b63a

Browse files
committed
[clangd] Rebuild dependent files when a header is saved.
Summary: Caveats: - only works when the header is changed in the editor and the editor provides the notification - we revalidate preambles for all open files (stat all their headers) rather than taking advantage of the fact that we know which file changed. This is much simpler! Fixes clangd/clangd#107 Reviewers: kadircet Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D77847
1 parent f22fbe3 commit 596b63a

File tree

6 files changed

+65
-12
lines changed

6 files changed

+65
-12
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,12 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
569569
{"version", getClangToolFullVersion("clangd")}}},
570570
{"capabilities",
571571
llvm::json::Object{
572-
{"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
572+
{"textDocumentSync",
573+
llvm::json::Object{
574+
{"openClose", true},
575+
{"change", (int)TextDocumentSyncKind::Incremental},
576+
{"save", true},
577+
}},
573578
{"documentFormattingProvider", true},
574579
{"documentRangeFormattingProvider", true},
575580
{"documentOnTypeFormattingProvider",
@@ -684,7 +689,16 @@ void ClangdLSPServer::onDocumentDidChange(
684689
WantDiags, Params.forceRebuild);
685690
}
686691

692+
void ClangdLSPServer::onDocumentDidSave(
693+
const DidSaveTextDocumentParams &Params) {
694+
reparseOpenFilesIfNeeded([](llvm::StringRef) { return true; });
695+
}
696+
687697
void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
698+
// We could also reparse all open files here. However:
699+
// - this could be frequent, and revalidating all the preambles isn't free
700+
// - this is useful e.g. when switching git branches, but we're likely to see
701+
// fresh headers but still have the old-branch main-file content
688702
Server->onFileEvent(Params);
689703
}
690704

@@ -1180,7 +1194,8 @@ void ClangdLSPServer::applyConfiguration(
11801194
}
11811195
}
11821196

1183-
reparseOpenedFiles(ModifiedFiles);
1197+
reparseOpenFilesIfNeeded(
1198+
[&](llvm::StringRef File) { return ModifiedFiles.count(File) != 0; });
11841199
}
11851200

11861201
void ClangdLSPServer::publishTheiaSemanticHighlighting(
@@ -1358,6 +1373,7 @@ ClangdLSPServer::ClangdLSPServer(
13581373
MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen);
13591374
MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose);
13601375
MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange);
1376+
MsgHandler->bind("textDocument/didSave", &ClangdLSPServer::onDocumentDidSave);
13611377
MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
13621378
MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
13631379
MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo);
@@ -1566,13 +1582,11 @@ void ClangdLSPServer::onFileUpdated(PathRef File, const TUStatus &Status) {
15661582
notify("textDocument/clangd.fileStatus", Status.render(File));
15671583
}
15681584

1569-
void ClangdLSPServer::reparseOpenedFiles(
1570-
const llvm::StringSet<> &ModifiedFiles) {
1571-
if (ModifiedFiles.empty())
1572-
return;
1585+
void ClangdLSPServer::reparseOpenFilesIfNeeded(
1586+
llvm::function_ref<bool(llvm::StringRef File)> Filter) {
15731587
// Reparse only opened files that were modified.
15741588
for (const Path &FilePath : DraftMgr.getActiveFiles())
1575-
if (ModifiedFiles.find(FilePath) != ModifiedFiles.end())
1589+
if (Filter(FilePath))
15761590
if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race?
15771591
Server->addDocument(FilePath, std::move(Draft->Contents),
15781592
encodeVersion(Draft->Version),

clang-tools-extra/clangd/ClangdLSPServer.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
7575
void onDocumentDidOpen(const DidOpenTextDocumentParams &);
7676
void onDocumentDidChange(const DidChangeTextDocumentParams &);
7777
void onDocumentDidClose(const DidCloseTextDocumentParams &);
78+
void onDocumentDidSave(const DidSaveTextDocumentParams &);
7879
void onDocumentOnTypeFormatting(const DocumentOnTypeFormattingParams &,
7980
Callback<std::vector<TextEdit>>);
8081
void onDocumentRangeFormatting(const DocumentRangeFormattingParams &,
@@ -131,10 +132,12 @@ class ClangdLSPServer : private ClangdServer::Callbacks {
131132
/// produce '->' and '::', respectively.
132133
bool shouldRunCompletion(const CompletionParams &Params) const;
133134

134-
/// Forces a reparse of all currently opened files which were modified. As a
135-
/// result, this method may be very expensive. This method is normally called
136-
/// when the compilation database is changed.
137-
void reparseOpenedFiles(const llvm::StringSet<> &ModifiedFiles);
135+
/// Requests a reparse of currently opened files using their latest source.
136+
/// This will typically only rebuild if something other than the source has
137+
/// changed (e.g. the CDB yields different flags, or files included in the
138+
/// preamble have been modified).
139+
void reparseOpenFilesIfNeeded(
140+
llvm::function_ref<bool(llvm::StringRef File)> Filter);
138141
void applyConfiguration(const ConfigurationSettings &Settings);
139142

140143
/// Sends a "publishSemanticHighlighting" notification to the LSP client.

clang-tools-extra/clangd/Protocol.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,11 @@ bool fromJSON(const llvm::json::Value &Params, DidCloseTextDocumentParams &R) {
452452
return O && O.map("textDocument", R.textDocument);
453453
}
454454

455+
bool fromJSON(const llvm::json::Value &Params, DidSaveTextDocumentParams &R) {
456+
llvm::json::ObjectMapper O(Params);
457+
return O && O.map("textDocument", R.textDocument);
458+
}
459+
455460
bool fromJSON(const llvm::json::Value &Params, DidChangeTextDocumentParams &R) {
456461
llvm::json::ObjectMapper O(Params);
457462
if (!O)

clang-tools-extra/clangd/Protocol.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,12 @@ struct DidCloseTextDocumentParams {
654654
};
655655
bool fromJSON(const llvm::json::Value &, DidCloseTextDocumentParams &);
656656

657+
struct DidSaveTextDocumentParams {
658+
/// The document that was saved.
659+
TextDocumentIdentifier textDocument;
660+
};
661+
bool fromJSON(const llvm::json::Value &, DidSaveTextDocumentParams &);
662+
657663
struct TextDocumentContentChangeEvent {
658664
/// The range of the document that changed.
659665
llvm::Optional<Range> range;

clang-tools-extra/clangd/test/initialize-params.test

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@
5656
# CHECK-NEXT: ","
5757
# CHECK-NEXT: ]
5858
# CHECK-NEXT: },
59-
# CHECK-NEXT: "textDocumentSync": 2,
59+
# CHECK-NEXT: "textDocumentSync": {
60+
# CHECK-NEXT: "change": 2,
61+
# CHECK-NEXT: "openClose": true,
62+
# CHECK-NEXT: "save": true
63+
# CHECK-NEXT: },
6064
# CHECK-NEXT: "typeHierarchyProvider": true
6165
# CHECK-NEXT: "workspaceSymbolProvider": true
6266
# CHECK-NEXT: },

clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,27 @@ TEST_F(LSPTest, Diagnostics) {
126126
EXPECT_THAT(Client.diagnostics("foo.cpp"), llvm::ValueIs(testing::IsEmpty()));
127127
}
128128

129+
TEST_F(LSPTest, DiagnosticsHeaderSaved) {
130+
auto &Client = start();
131+
FS.Files["foo.h"] = "#define VAR original";
132+
Client.didOpen("foo.cpp", R"cpp(
133+
#include "foo.h"
134+
int x = VAR;
135+
)cpp");
136+
EXPECT_THAT(Client.diagnostics("foo.cpp"),
137+
llvm::ValueIs(testing::ElementsAre(
138+
DiagMessage("Use of undeclared identifier 'original'"))));
139+
// Now modify the header from within the "editor".
140+
FS.Files["foo.h"] = "#define VAR changed";
141+
Client.notify(
142+
"textDocument/didSave",
143+
llvm::json::Object{{"textDocument", Client.documentID("foo.h")}});
144+
// Foo.cpp should be rebuilt with new diagnostics.
145+
EXPECT_THAT(Client.diagnostics("foo.cpp"),
146+
llvm::ValueIs(testing::ElementsAre(
147+
DiagMessage("Use of undeclared identifier 'changed'"))));
148+
}
149+
129150
} // namespace
130151
} // namespace clangd
131152
} // namespace clang

0 commit comments

Comments
 (0)