| // Copyright 2021 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/lsp/lsp-text-buffer.h" | |
| #include "common/strings/utf8.h" | |
| namespace verible { | |
| namespace lsp { | |
| EditTextBuffer::EditTextBuffer(absl::string_view initial_text) { | |
| ReplaceDocument(initial_text); | |
| } | |
| void EditTextBuffer::ApplyChanges( | |
| const std::vector<TextDocumentContentChangeEvent> &cc) { | |
| for (const auto &c : cc) ApplyChange(c); | |
| } | |
| bool EditTextBuffer::ApplyChange(const TextDocumentContentChangeEvent &c) { | |
| if (!c.has_range) { | |
| ReplaceDocument(c.text); | |
| return true; | |
| } | |
| if (c.range.end.line >= static_cast<int>(lines_.size())) { | |
| lines_.emplace_back(new std::string("")); | |
| } | |
| if (c.range.start.line == c.range.end.line && | |
| c.text.find_first_of('\n') == std::string::npos) { | |
| return LineEdit(c, lines_[c.range.start.line].get()); // simple case. | |
| } else { | |
| return MultiLineEdit(c); | |
| } | |
| } | |
| /*static*/ EditTextBuffer::LineVector EditTextBuffer::GenerateLines( | |
| absl::string_view content) { | |
| LineVector result; | |
| for (const absl::string_view s : absl::StrSplit(content, '\n')) { | |
| result.emplace_back(new std::string(s)); | |
| result.back()->append("\n"); | |
| } | |
| // Files that do or do not have a newline file-ending: represent correctly. | |
| if (!content.empty() && content.back() == '\n') { | |
| result.pop_back(); | |
| } else { | |
| result.back()->pop_back(); | |
| } | |
| return result; | |
| } | |
| void EditTextBuffer::ReplaceDocument(absl::string_view content) { | |
| document_length_ = content.length(); | |
| if (content.empty()) return; | |
| lines_ = GenerateLines(content); | |
| } | |
| // Return success (might not if input out of range) | |
| bool EditTextBuffer::LineEdit(const TextDocumentContentChangeEvent &c, | |
| std::string *str) { | |
| int end_char = c.range.end.character; | |
| int str_end = utf8_len(*str); | |
| if (!str->empty() && str->back() == '\n') --str_end; | |
| if (c.range.start.character > str_end) return false; | |
| if (end_char > str_end) end_char = str_end; | |
| if (end_char < c.range.start.character) return false; | |
| document_length_ -= str->length(); | |
| const absl::string_view assembly = *str; | |
| const auto before = utf8_substr(assembly, 0, c.range.start.character); | |
| const auto after = utf8_substr(assembly, end_char); | |
| *str = absl::StrCat(before, c.text, after); | |
| document_length_ += str->length(); | |
| return true; | |
| } | |
| // Returns success (always succeeds); | |
| bool EditTextBuffer::MultiLineEdit(const TextDocumentContentChangeEvent &c) { | |
| const absl::string_view start_line = *lines_[c.range.start.line]; | |
| const auto before = utf8_substr(start_line, 0, c.range.start.character); | |
| const absl::string_view end_line = *lines_[c.range.end.line]; | |
| const auto after = utf8_substr(end_line, c.range.end.character); | |
| // Assemble the full content to replace the range of lines with including | |
| // the parts that come from the first and last line to be edited. | |
| const std::string new_content = absl::StrCat(before, c.text, after); | |
| // Content length update: substract all the bytes that were in the old | |
| // content and add all in the new content. | |
| const auto before_begin = lines_.begin() + c.range.start.line; | |
| const auto before_end = lines_.begin() + c.range.end.line + 1; | |
| document_length_ -= | |
| std::accumulate(before_begin, before_end, 0, | |
| [](int sum, const LineVector::value_type &line) { | |
| return sum + line->length(); | |
| }); | |
| document_length_ += new_content.length(); | |
| // The new content might include newlines, yielding multiple single lines. | |
| LineVector regenerated_lines = GenerateLines(new_content); | |
| // Update the affected lines. Probably not the most optimal but good enough | |
| lines_.erase(before_begin, before_end); | |
| lines_.insert(lines_.begin() + c.range.start.line, regenerated_lines.begin(), | |
| regenerated_lines.end()); | |
| return true; | |
| } | |
| BufferCollection::BufferCollection(JsonRpcDispatcher *dispatcher) { | |
| // Route notification events from the dispatcher to the buffer collection | |
| // for them to keep track of what buffers are open and all of their edits | |
| // they receive. | |
| dispatcher->AddNotificationHandler( | |
| "textDocument/didOpen", | |
| [this](const DidOpenTextDocumentParams &p) { didOpenEvent(p); }); | |
| dispatcher->AddNotificationHandler( | |
| "textDocument/didClose", | |
| [this](const DidCloseTextDocumentParams &p) { didCloseEvent(p); }); | |
| dispatcher->AddNotificationHandler( | |
| "textDocument/didChange", | |
| [this](const DidChangeTextDocumentParams &p) { didChangeEvent(p); }); | |
| } | |
| void BufferCollection::didOpenEvent(const DidOpenTextDocumentParams &o) { | |
| auto inserted = buffers_.insert({o.textDocument.uri, nullptr}); | |
| if (inserted.second) { | |
| inserted.first->second.reset(new EditTextBuffer(o.textDocument.text)); | |
| inserted.first->second->set_last_global_version(++global_version_); | |
| if (change_listener_) | |
| change_listener_(o.textDocument.uri, inserted.first->second.get()); | |
| } | |
| } | |
| void BufferCollection::didCloseEvent(const DidCloseTextDocumentParams &o) { | |
| if (change_listener_) { | |
| // Let's call the callback first in case our users still have a dangling | |
| // reference. | |
| change_listener_(o.textDocument.uri, nullptr); | |
| } | |
| buffers_.erase(o.textDocument.uri); | |
| } | |
| void BufferCollection::didChangeEvent(const DidChangeTextDocumentParams &o) { | |
| auto found = buffers_.find(o.textDocument.uri); | |
| if (found == buffers_.end()) return; | |
| EditTextBuffer *const buffer = found->second.get(); | |
| buffer->ApplyChanges(o.contentChanges); | |
| buffer->set_last_global_version(++global_version_); | |
| if (change_listener_) change_listener_(o.textDocument.uri, buffer); | |
| } | |
| void EditTextBuffer::RequestContent(const ContentProcessFun &processor) const { | |
| std::string flat_view; | |
| flat_view.reserve(document_length_); | |
| for (const auto &l : lines_) flat_view.append(*l); | |
| processor(flat_view); | |
| } | |
| void EditTextBuffer::RequestLine(int line, | |
| const ContentProcessFun &processor) const { | |
| if (line < 0 || line >= static_cast<int>(lines_.size())) { | |
| processor(""); | |
| } else { | |
| processor(*lines_[line]); | |
| } | |
| } | |
| } // namespace lsp | |
| } // namespace verible |