blob: 415c3f15a41ba22b50fc32da9645c9d4b318233d [file] [log] [blame]
// 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