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