1 //===--- DraftStore.cpp - File contents container ---------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "DraftStore.h"
10 #include "SourceCode.h"
11 #include "support/Logger.h"
12 #include "llvm/Support/Errc.h"
13
14 namespace clang {
15 namespace clangd {
16
getDraft(PathRef File) const17 llvm::Optional<DraftStore::Draft> DraftStore::getDraft(PathRef File) const {
18 std::lock_guard<std::mutex> Lock(Mutex);
19
20 auto It = Drafts.find(File);
21 if (It == Drafts.end())
22 return None;
23
24 return It->second;
25 }
26
getActiveFiles() const27 std::vector<Path> DraftStore::getActiveFiles() const {
28 std::lock_guard<std::mutex> Lock(Mutex);
29 std::vector<Path> ResultVector;
30
31 for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++)
32 ResultVector.push_back(std::string(DraftIt->getKey()));
33
34 return ResultVector;
35 }
36
updateVersion(DraftStore::Draft & D,llvm::Optional<int64_t> Version)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
addDraft(PathRef File,llvm::Optional<int64_t> Version,llvm::StringRef Contents)50 int64_t DraftStore::addDraft(PathRef File, llvm::Optional<int64_t> Version,
51 llvm::StringRef Contents) {
52 std::lock_guard<std::mutex> Lock(Mutex);
53
54 Draft &D = Drafts[File];
55 updateVersion(D, Version);
56 D.Contents = Contents.str();
57 return D.Version;
58 }
59
updateDraft(PathRef File,llvm::Optional<int64_t> Version,llvm::ArrayRef<TextDocumentContentChangeEvent> Changes)60 llvm::Expected<DraftStore::Draft> DraftStore::updateDraft(
61 PathRef File, llvm::Optional<int64_t> Version,
62 llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) {
63 std::lock_guard<std::mutex> Lock(Mutex);
64
65 auto EntryIt = Drafts.find(File);
66 if (EntryIt == Drafts.end()) {
67 return error(llvm::errc::invalid_argument,
68 "Trying to do incremental update on non-added document: {0}",
69 File);
70 }
71 Draft &D = EntryIt->second;
72 std::string Contents = EntryIt->second.Contents;
73
74 for (const TextDocumentContentChangeEvent &Change : Changes) {
75 if (!Change.range) {
76 Contents = Change.text;
77 continue;
78 }
79
80 const Position &Start = Change.range->start;
81 llvm::Expected<size_t> StartIndex =
82 positionToOffset(Contents, Start, false);
83 if (!StartIndex)
84 return StartIndex.takeError();
85
86 const Position &End = Change.range->end;
87 llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false);
88 if (!EndIndex)
89 return EndIndex.takeError();
90
91 if (*EndIndex < *StartIndex)
92 return error(llvm::errc::invalid_argument,
93 "Range's end position ({0}) is before start position ({1})",
94 End, Start);
95
96 // Since the range length between two LSP positions is dependent on the
97 // contents of the buffer we compute the range length between the start and
98 // end position ourselves and compare it to the range length of the LSP
99 // message to verify the buffers of the client and server are in sync.
100
101 // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16
102 // code units.
103 ssize_t ComputedRangeLength =
104 lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));
105
106 if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength)
107 return error(llvm::errc::invalid_argument,
108 "Change's rangeLength ({0}) doesn't match the "
109 "computed range length ({1}).",
110 *Change.rangeLength, ComputedRangeLength);
111
112 std::string NewContents;
113 NewContents.reserve(*StartIndex + Change.text.length() +
114 (Contents.length() - *EndIndex));
115
116 NewContents = Contents.substr(0, *StartIndex);
117 NewContents += Change.text;
118 NewContents += Contents.substr(*EndIndex);
119
120 Contents = std::move(NewContents);
121 }
122
123 updateVersion(D, Version);
124 D.Contents = std::move(Contents);
125 return D;
126 }
127
removeDraft(PathRef File)128 void DraftStore::removeDraft(PathRef File) {
129 std::lock_guard<std::mutex> Lock(Mutex);
130
131 Drafts.erase(File);
132 }
133
134 } // namespace clangd
135 } // namespace clang
136