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