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 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 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 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