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 "Logger.h"
11 #include "SourceCode.h"
12 #include "llvm/Support/Errc.h"
13 
14 namespace clang {
15 namespace clangd {
16 
17 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 
27 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 
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) {
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 
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 llvm::make_error<llvm::StringError>(
68         "Trying to do incremental update on non-added document: " + File,
69         llvm::errc::invalid_argument);
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 llvm::make_error<llvm::StringError>(
93           llvm::formatv(
94               "Range's end position ({0}) is before start position ({1})", End,
95               Start),
96           llvm::errc::invalid_argument);
97 
98     // Since the range length between two LSP positions is dependent on the
99     // contents of the buffer we compute the range length between the start and
100     // end position ourselves and compare it to the range length of the LSP
101     // message to verify the buffers of the client and server are in sync.
102 
103     // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16
104     // code units.
105     ssize_t ComputedRangeLength =
106         lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));
107 
108     if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength)
109       return llvm::make_error<llvm::StringError>(
110           llvm::formatv("Change's rangeLength ({0}) doesn't match the "
111                         "computed range length ({1}).",
112                         *Change.rangeLength, ComputedRangeLength),
113           llvm::errc::invalid_argument);
114 
115     std::string NewContents;
116     NewContents.reserve(*StartIndex + Change.text.length() +
117                         (Contents.length() - *EndIndex));
118 
119     NewContents = Contents.substr(0, *StartIndex);
120     NewContents += Change.text;
121     NewContents += Contents.substr(*EndIndex);
122 
123     Contents = std::move(NewContents);
124   }
125 
126   updateVersion(D, Version);
127   D.Contents = std::move(Contents);
128   return D;
129 }
130 
131 void DraftStore::removeDraft(PathRef File) {
132   std::lock_guard<std::mutex> Lock(Mutex);
133 
134   Drafts.erase(File);
135 }
136 
137 } // namespace clangd
138 } // namespace clang
139