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 namespace clang {
15 namespace clangd {
16 
17 llvm::Optional<std::string> 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(DraftIt->getKey());
33 
34   return ResultVector;
35 }
36 
37 void DraftStore::addDraft(PathRef File, llvm::StringRef Contents) {
38   std::lock_guard<std::mutex> Lock(Mutex);
39 
40   Drafts[File] = Contents;
41 }
42 
43 llvm::Expected<std::string> DraftStore::updateDraft(
44     PathRef File, llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) {
45   std::lock_guard<std::mutex> Lock(Mutex);
46 
47   auto EntryIt = Drafts.find(File);
48   if (EntryIt == Drafts.end()) {
49     return llvm::make_error<llvm::StringError>(
50         "Trying to do incremental update on non-added document: " + File,
51         llvm::errc::invalid_argument);
52   }
53 
54   std::string Contents = EntryIt->second;
55 
56   for (const TextDocumentContentChangeEvent &Change : Changes) {
57     if (!Change.range) {
58       Contents = Change.text;
59       continue;
60     }
61 
62     const Position &Start = Change.range->start;
63     llvm::Expected<size_t> StartIndex =
64         positionToOffset(Contents, Start, false);
65     if (!StartIndex)
66       return StartIndex.takeError();
67 
68     const Position &End = Change.range->end;
69     llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false);
70     if (!EndIndex)
71       return EndIndex.takeError();
72 
73     if (*EndIndex < *StartIndex)
74       return llvm::make_error<llvm::StringError>(
75           llvm::formatv(
76               "Range's end position ({0}) is before start position ({1})", End,
77               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 llvm::make_error<llvm::StringError>(
92           llvm::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