1 //===--- DraftStore.cpp - File contents container ---------------*- C++ -*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "DraftStore.h"
11 #include "SourceCode.h"
12 #include "llvm/Support/Errc.h"
13 
14 using namespace llvm;
15 namespace clang {
16 namespace clangd {
17 
18 Optional<std::string> DraftStore::getDraft(PathRef File) const {
19   std::lock_guard<std::mutex> Lock(Mutex);
20 
21   auto It = Drafts.find(File);
22   if (It == Drafts.end())
23     return None;
24 
25   return It->second;
26 }
27 
28 std::vector<Path> DraftStore::getActiveFiles() const {
29   std::lock_guard<std::mutex> Lock(Mutex);
30   std::vector<Path> ResultVector;
31 
32   for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++)
33     ResultVector.push_back(DraftIt->getKey());
34 
35   return ResultVector;
36 }
37 
38 void DraftStore::addDraft(PathRef File, StringRef Contents) {
39   std::lock_guard<std::mutex> Lock(Mutex);
40 
41   Drafts[File] = Contents;
42 }
43 
44 Expected<std::string>
45 DraftStore::updateDraft(PathRef File,
46                         ArrayRef<TextDocumentContentChangeEvent> Changes) {
47   std::lock_guard<std::mutex> Lock(Mutex);
48 
49   auto EntryIt = Drafts.find(File);
50   if (EntryIt == Drafts.end()) {
51     return make_error<StringError>(
52         "Trying to do incremental update on non-added document: " + File,
53         llvm::errc::invalid_argument);
54   }
55 
56   std::string Contents = EntryIt->second;
57 
58   for (const TextDocumentContentChangeEvent &Change : Changes) {
59     if (!Change.range) {
60       Contents = Change.text;
61       continue;
62     }
63 
64     const Position &Start = Change.range->start;
65     Expected<size_t> StartIndex = positionToOffset(Contents, Start, false);
66     if (!StartIndex)
67       return StartIndex.takeError();
68 
69     const Position &End = Change.range->end;
70     Expected<size_t> EndIndex = positionToOffset(Contents, End, false);
71     if (!EndIndex)
72       return EndIndex.takeError();
73 
74     if (*EndIndex < *StartIndex)
75       return make_error<StringError>(
76           formatv("Range's end position ({0}) is before start position ({1})",
77                   End, Start),
78           llvm::errc::invalid_argument);
79 
80     // Since the range length between two LSP positions is dependent on the
81     // contents of the buffer we compute the range length between the start and
82     // end position ourselves and compare it to the range length of the LSP
83     // message to verify the buffers of the client and server are in sync.
84 
85     // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16
86     // code units.
87     ssize_t ComputedRangeLength =
88         lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex));
89 
90     if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength)
91       return make_error<StringError>(
92           formatv("Change's rangeLength ({0}) doesn't match the "
93                   "computed range length ({1}).",
94                   *Change.rangeLength, *EndIndex - *StartIndex),
95           llvm::errc::invalid_argument);
96 
97     std::string NewContents;
98     NewContents.reserve(*StartIndex + Change.text.length() +
99                         (Contents.length() - *EndIndex));
100 
101     NewContents = Contents.substr(0, *StartIndex);
102     NewContents += Change.text;
103     NewContents += Contents.substr(*EndIndex);
104 
105     Contents = std::move(NewContents);
106   }
107 
108   EntryIt->second = Contents;
109   return Contents;
110 }
111 
112 void DraftStore::removeDraft(PathRef File) {
113   std::lock_guard<std::mutex> Lock(Mutex);
114 
115   Drafts.erase(File);
116 }
117 
118 } // namespace clangd
119 } // namespace clang
120