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