| //===--- DraftStore.cpp - File contents container ---------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DraftStore.h" |
| #include "SourceCode.h" |
| #include "support/Logger.h" |
| #include "llvm/Support/Errc.h" |
| |
| namespace clang { |
| namespace clangd { |
| |
| llvm::Optional<DraftStore::Draft> DraftStore::getDraft(PathRef File) const { |
| std::lock_guard<std::mutex> Lock(Mutex); |
| |
| auto It = Drafts.find(File); |
| if (It == Drafts.end()) |
| return None; |
| |
| return It->second; |
| } |
| |
| std::vector<Path> DraftStore::getActiveFiles() const { |
| std::lock_guard<std::mutex> Lock(Mutex); |
| std::vector<Path> ResultVector; |
| |
| for (auto DraftIt = Drafts.begin(); DraftIt != Drafts.end(); DraftIt++) |
| ResultVector.push_back(std::string(DraftIt->getKey())); |
| |
| return ResultVector; |
| } |
| |
| static void updateVersion(DraftStore::Draft &D, |
| llvm::Optional<int64_t> Version) { |
| if (Version) { |
| // We treat versions as opaque, but the protocol says they increase. |
| if (*Version <= D.Version) |
| log("File version went from {0} to {1}", D.Version, Version); |
| D.Version = *Version; |
| } else { |
| // Note that if D was newly-created, this will bump D.Version from -1 to 0. |
| ++D.Version; |
| } |
| } |
| |
| int64_t DraftStore::addDraft(PathRef File, llvm::Optional<int64_t> Version, |
| llvm::StringRef Contents) { |
| std::lock_guard<std::mutex> Lock(Mutex); |
| |
| Draft &D = Drafts[File]; |
| updateVersion(D, Version); |
| D.Contents = Contents.str(); |
| return D.Version; |
| } |
| |
| llvm::Expected<DraftStore::Draft> DraftStore::updateDraft( |
| PathRef File, llvm::Optional<int64_t> Version, |
| llvm::ArrayRef<TextDocumentContentChangeEvent> Changes) { |
| std::lock_guard<std::mutex> Lock(Mutex); |
| |
| auto EntryIt = Drafts.find(File); |
| if (EntryIt == Drafts.end()) { |
| return llvm::make_error<llvm::StringError>( |
| "Trying to do incremental update on non-added document: " + File, |
| llvm::errc::invalid_argument); |
| } |
| Draft &D = EntryIt->second; |
| std::string Contents = EntryIt->second.Contents; |
| |
| for (const TextDocumentContentChangeEvent &Change : Changes) { |
| if (!Change.range) { |
| Contents = Change.text; |
| continue; |
| } |
| |
| const Position &Start = Change.range->start; |
| llvm::Expected<size_t> StartIndex = |
| positionToOffset(Contents, Start, false); |
| if (!StartIndex) |
| return StartIndex.takeError(); |
| |
| const Position &End = Change.range->end; |
| llvm::Expected<size_t> EndIndex = positionToOffset(Contents, End, false); |
| if (!EndIndex) |
| return EndIndex.takeError(); |
| |
| if (*EndIndex < *StartIndex) |
| return llvm::make_error<llvm::StringError>( |
| llvm::formatv( |
| "Range's end position ({0}) is before start position ({1})", End, |
| Start), |
| llvm::errc::invalid_argument); |
| |
| // Since the range length between two LSP positions is dependent on the |
| // contents of the buffer we compute the range length between the start and |
| // end position ourselves and compare it to the range length of the LSP |
| // message to verify the buffers of the client and server are in sync. |
| |
| // EndIndex and StartIndex are in bytes, but Change.rangeLength is in UTF-16 |
| // code units. |
| ssize_t ComputedRangeLength = |
| lspLength(Contents.substr(*StartIndex, *EndIndex - *StartIndex)); |
| |
| if (Change.rangeLength && ComputedRangeLength != *Change.rangeLength) |
| return llvm::make_error<llvm::StringError>( |
| llvm::formatv("Change's rangeLength ({0}) doesn't match the " |
| "computed range length ({1}).", |
| *Change.rangeLength, ComputedRangeLength), |
| llvm::errc::invalid_argument); |
| |
| std::string NewContents; |
| NewContents.reserve(*StartIndex + Change.text.length() + |
| (Contents.length() - *EndIndex)); |
| |
| NewContents = Contents.substr(0, *StartIndex); |
| NewContents += Change.text; |
| NewContents += Contents.substr(*EndIndex); |
| |
| Contents = std::move(NewContents); |
| } |
| |
| updateVersion(D, Version); |
| D.Contents = std::move(Contents); |
| return D; |
| } |
| |
| void DraftStore::removeDraft(PathRef File) { |
| std::lock_guard<std::mutex> Lock(Mutex); |
| |
| Drafts.erase(File); |
| } |
| |
| } // namespace clangd |
| } // namespace clang |