Skip to content

Commit caf5a4d

Browse files
committed
[clangd] Propagate versions into DraftStore, assigning where missing. NFC
This prepares for propagating versions through the server so diagnostics etc can be versioned.
1 parent dfe8f5d commit caf5a4d

File tree

6 files changed

+159
-58
lines changed

6 files changed

+159
-58
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ llvm::Error validateEdits(const DraftStore &DraftMgr, const FileEdits &FE) {
117117
// If the file is open in user's editor, make sure the version we
118118
// saw and current version are compatible as this is the text that
119119
// will be replaced by editors.
120-
if (!It.second.canApplyTo(*Draft)) {
120+
if (!It.second.canApplyTo(Draft->Contents)) {
121121
++InvalidFileCount;
122122
LastInvalidFile = It.first();
123123
}
@@ -630,7 +630,7 @@ void ClangdLSPServer::onDocumentDidOpen(
630630

631631
const std::string &Contents = Params.textDocument.text;
632632

633-
DraftMgr.addDraft(File, Contents);
633+
DraftMgr.addDraft(File, Params.textDocument.version, Contents);
634634
Server->addDocument(File, Contents, WantDiagnostics::Yes);
635635
}
636636

@@ -642,19 +642,19 @@ void ClangdLSPServer::onDocumentDidChange(
642642
: WantDiagnostics::No;
643643

644644
PathRef File = Params.textDocument.uri.file();
645-
llvm::Expected<std::string> Contents =
646-
DraftMgr.updateDraft(File, Params.contentChanges);
647-
if (!Contents) {
645+
llvm::Expected<DraftStore::Draft> Draft = DraftMgr.updateDraft(
646+
File, Params.textDocument.version, Params.contentChanges);
647+
if (!Draft) {
648648
// If this fails, we are most likely going to be not in sync anymore with
649649
// the client. It is better to remove the draft and let further operations
650650
// fail rather than giving wrong results.
651651
DraftMgr.removeDraft(File);
652652
Server->removeDocument(File);
653-
elog("Failed to update {0}: {1}", File, Contents.takeError());
653+
elog("Failed to update {0}: {1}", File, Draft.takeError());
654654
return;
655655
}
656656

657-
Server->addDocument(File, *Contents, WantDiags, Params.forceRebuild);
657+
Server->addDocument(File, Draft->Contents, WantDiags, Params.forceRebuild);
658658
}
659659

660660
void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
@@ -773,8 +773,7 @@ void ClangdLSPServer::onPrepareRename(const TextDocumentPositionParams &Params,
773773
void ClangdLSPServer::onRename(const RenameParams &Params,
774774
Callback<WorkspaceEdit> Reply) {
775775
Path File = std::string(Params.textDocument.uri.file());
776-
llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
777-
if (!Code)
776+
if (!DraftMgr.getDraft(File))
778777
return Reply(llvm::make_error<LSPError>(
779778
"onRename called for non-added file", ErrorCode::InvalidParams));
780779
Server->rename(
@@ -829,7 +828,7 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
829828
"onDocumentOnTypeFormatting called for non-added file",
830829
ErrorCode::InvalidParams));
831830

832-
Reply(Server->formatOnType(*Code, File, Params.position, Params.ch));
831+
Reply(Server->formatOnType(Code->Contents, File, Params.position, Params.ch));
833832
}
834833

835834
void ClangdLSPServer::onDocumentRangeFormatting(
@@ -842,9 +841,10 @@ void ClangdLSPServer::onDocumentRangeFormatting(
842841
"onDocumentRangeFormatting called for non-added file",
843842
ErrorCode::InvalidParams));
844843

845-
auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range);
844+
auto ReplacementsOrError =
845+
Server->formatRange(Code->Contents, File, Params.range);
846846
if (ReplacementsOrError)
847-
Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
847+
Reply(replacementsToEdits(Code->Contents, ReplacementsOrError.get()));
848848
else
849849
Reply(ReplacementsOrError.takeError());
850850
}
@@ -859,9 +859,9 @@ void ClangdLSPServer::onDocumentFormatting(
859859
"onDocumentFormatting called for non-added file",
860860
ErrorCode::InvalidParams));
861861

862-
auto ReplacementsOrError = Server->formatFile(*Code, File);
862+
auto ReplacementsOrError = Server->formatFile(Code->Contents, File);
863863
if (ReplacementsOrError)
864-
Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
864+
Reply(replacementsToEdits(Code->Contents, ReplacementsOrError.get()));
865865
else
866866
Reply(ReplacementsOrError.takeError());
867867
}
@@ -1328,7 +1328,7 @@ bool ClangdLSPServer::shouldRunCompletion(
13281328
// Running the lexer here would be more robust (e.g. we can detect comments
13291329
// and avoid triggering completion there), but we choose to err on the side
13301330
// of simplicity here.
1331-
auto Offset = positionToOffset(*Code, Params.position,
1331+
auto Offset = positionToOffset(Code->Contents, Params.position,
13321332
/*AllowColumnsBeyondLineLength=*/false);
13331333
if (!Offset) {
13341334
vlog("could not convert position '{0}' to offset for file '{1}'",
@@ -1339,9 +1339,9 @@ bool ClangdLSPServer::shouldRunCompletion(
13391339
return false;
13401340

13411341
if (Trigger == ">")
1342-
return (*Code)[*Offset - 2] == '-'; // trigger only on '->'.
1342+
return Code->Contents[*Offset - 2] == '-'; // trigger only on '->'.
13431343
if (Trigger == ":")
1344-
return (*Code)[*Offset - 2] == ':'; // trigger only on '::'.
1344+
return Code->Contents[*Offset - 2] == ':'; // trigger only on '::'.
13451345
assert(false && "unhandled trigger character");
13461346
return true;
13471347
}
@@ -1475,7 +1475,7 @@ void ClangdLSPServer::reparseOpenedFiles(
14751475
// Reparse only opened files that were modified.
14761476
for (const Path &FilePath : DraftMgr.getActiveFiles())
14771477
if (ModifiedFiles.find(FilePath) != ModifiedFiles.end())
1478-
Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath),
1478+
Server->addDocument(FilePath, DraftMgr.getDraft(FilePath)->Contents,
14791479
WantDiagnostics::Auto);
14801480
}
14811481

clang-tools-extra/clangd/DraftStore.cpp

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "DraftStore.h"
10+
#include "Logger.h"
1011
#include "SourceCode.h"
1112
#include "llvm/Support/Errc.h"
1213

1314
namespace clang {
1415
namespace clangd {
1516

16-
llvm::Optional<std::string> DraftStore::getDraft(PathRef File) const {
17+
llvm::Optional<DraftStore::Draft> DraftStore::getDraft(PathRef File) const {
1718
std::lock_guard<std::mutex> Lock(Mutex);
1819

1920
auto It = Drafts.find(File);
@@ -33,14 +34,32 @@ std::vector<Path> DraftStore::getActiveFiles() const {
3334
return ResultVector;
3435
}
3536

36-
void DraftStore::addDraft(PathRef File, llvm::StringRef Contents) {
37+
static void updateVersion(DraftStore::Draft &D,
38+
llvm::Optional<int64_t> Version) {
39+
if (Version) {
40+
// We treat versions as opaque, but the protocol says they increase.
41+
if (*Version <= D.Version)
42+
log("File version went from {0} to {1}", D.Version, Version);
43+
D.Version = *Version;
44+
} else {
45+
// Note that if D was newly-created, this will bump D.Version from -1 to 0.
46+
++D.Version;
47+
}
48+
}
49+
50+
int64_t DraftStore::addDraft(PathRef File, llvm::Optional<int64_t> Version,
51+
llvm::StringRef Contents) {
3752
std::lock_guard<std::mutex> Lock(Mutex);
3853

39-
Drafts[File] = std::string(Contents);
54+
Draft &D = Drafts[File];
55+
updateVersion(D, Version);
56+
D.Contents = Contents.str();
57+
return D.Version;
4058
}
4159

42-
llvm::Expected<std::string> DraftStore::updateDraft(
43-
PathRef File, llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) {
60+
llvm::Expected<DraftStore::Draft> DraftStore::updateDraft(
61+
PathRef File, llvm::Optional<int64_t> Version,
62+
llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) {
4463
std::lock_guard<std::mutex> Lock(Mutex);
4564

4665
auto EntryIt = Drafts.find(File);
@@ -49,8 +68,8 @@ llvm::Expected<std::string> DraftStore::updateDraft(
4968
"Trying to do incremental update on non-added document: " + File,
5069
llvm::errc::invalid_argument);
5170
}
52-
53-
std::string Contents = EntryIt->second;
71+
Draft &D = EntryIt->second;
72+
std::string Contents = EntryIt->second.Contents;
5473

5574
for (const TextDocumentContentChangeEvent &Change : Changes) {
5675
if (!Change.range) {
@@ -104,8 +123,9 @@ llvm::Expected<std::string> DraftStore::updateDraft(
104123
Contents = std::move(NewContents);
105124
}
106125

107-
EntryIt->second = Contents;
108-
return Contents;
126+
updateVersion(D, Version);
127+
D.Contents = std::move(Contents);
128+
return D;
109129
}
110130

111131
void DraftStore::removeDraft(PathRef File) {

clang-tools-extra/clangd/DraftStore.h

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,45 @@ namespace clangd {
2323
/// A thread-safe container for files opened in a workspace, addressed by
2424
/// filenames. The contents are owned by the DraftStore. This class supports
2525
/// both whole and incremental updates of the documents.
26+
/// Each time a draft is updated, it is assigned a version number. This can be
27+
/// specified by the caller or incremented from the previous version.
2628
class DraftStore {
2729
public:
30+
struct Draft {
31+
std::string Contents;
32+
int64_t Version = -1;
33+
};
34+
2835
/// \return Contents of the stored document.
2936
/// For untracked files, a llvm::None is returned.
30-
llvm::Optional<std::string> getDraft(PathRef File) const;
37+
llvm::Optional<Draft> getDraft(PathRef File) const;
3138

3239
/// \return List of names of the drafts in this store.
3340
std::vector<Path> getActiveFiles() const;
3441

3542
/// Replace contents of the draft for \p File with \p Contents.
36-
void addDraft(PathRef File, StringRef Contents);
43+
/// If no version is specified, one will be automatically assigned.
44+
/// Returns the version.
45+
int64_t addDraft(PathRef File, llvm::Optional<int64_t> Version,
46+
StringRef Contents);
3747

3848
/// Update the contents of the draft for \p File based on \p Changes.
3949
/// If a position in \p Changes is invalid (e.g. out-of-range), the
4050
/// draft is not modified.
51+
/// If no version is specified, one will be automatically assigned.
4152
///
4253
/// \return The new version of the draft for \p File, or an error if the
4354
/// changes couldn't be applied.
44-
llvm::Expected<std::string>
45-
updateDraft(PathRef File,
55+
llvm::Expected<Draft>
56+
updateDraft(PathRef File, llvm::Optional<int64_t> Version,
4657
llvm::ArrayRef<TextDocumentContentChangeEvent> Changes);
4758

4859
/// Remove the draft from the store.
4960
void removeDraft(PathRef File);
5061

5162
private:
5263
mutable std::mutex Mutex;
53-
llvm::StringMap<std::string> Drafts;
64+
llvm::StringMap<Draft> Drafts;
5465
};
5566

5667
} // namespace clangd

clang-tools-extra/clangd/Protocol.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ bool fromJSON(const llvm::json::Value &Params, TextDocumentIdentifier &R) {
9090
return O && O.map("uri", R.uri);
9191
}
9292

93+
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &R) {
94+
auto Result = toJSON(static_cast<const TextDocumentIdentifier &>(R));
95+
if (R.version)
96+
Result.getAsObject()->try_emplace("version", R.version);
97+
return Result;
98+
}
99+
100+
bool fromJSON(const llvm::json::Value &Params,
101+
VersionedTextDocumentIdentifier &R) {
102+
llvm::json::ObjectMapper O(Params);
103+
return fromJSON(Params, static_cast<TextDocumentIdentifier &>(R)) && O &&
104+
O.map("version", R.version);
105+
}
106+
93107
bool fromJSON(const llvm::json::Value &Params, Position &R) {
94108
llvm::json::ObjectMapper O(Params);
95109
return O && O.map("line", R.line) && O.map("character", R.character);

clang-tools-extra/clangd/Protocol.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,20 @@ struct TextDocumentIdentifier {
124124
llvm::json::Value toJSON(const TextDocumentIdentifier &);
125125
bool fromJSON(const llvm::json::Value &, TextDocumentIdentifier &);
126126

127+
struct VersionedTextDocumentIdentifier : public TextDocumentIdentifier {
128+
// The version number of this document. If a versioned text document
129+
// identifier is sent from the server to the client and the file is not open
130+
// in the editor (the server has not received an open notification before) the
131+
// server can send `null` to indicate that the version is known and the
132+
// content on disk is the master (as speced with document content ownership).
133+
//
134+
// The version number of a document will increase after each change, including
135+
// undo/redo. The number doesn't need to be consecutive.
136+
llvm::Optional<std::int64_t> version;
137+
};
138+
llvm::json::Value toJSON(const VersionedTextDocumentIdentifier &);
139+
bool fromJSON(const llvm::json::Value &, VersionedTextDocumentIdentifier &);
140+
127141
struct Position {
128142
/// Line position in a document (zero-based).
129143
int line = 0;
@@ -223,7 +237,7 @@ struct TextDocumentItem {
223237
std::string languageId;
224238

225239
/// The version number of this document (it will strictly increase after each
226-
int version = 0;
240+
std::int64_t version = 0;
227241

228242
/// The content of the opened text document.
229243
std::string text;
@@ -643,7 +657,7 @@ struct DidChangeTextDocumentParams {
643657
/// The document that did change. The version number points
644658
/// to the version after all provided content changes have
645659
/// been applied.
646-
TextDocumentIdentifier textDocument;
660+
VersionedTextDocumentIdentifier textDocument;
647661

648662
/// The actual content changes.
649663
std::vector<TextDocumentContentChangeEvent> contentChanges;

0 commit comments

Comments
 (0)