blob: 626924e808ccd44a96a673148d14a9d6d014108f [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 "absl/strings/str_cat.h"
#include "common/lsp/json-rpc-dispatcher.h"
#include "gtest/gtest.h"
namespace verible {
namespace lsp {
TEST(TextBufferTest, RecreateEmptyFile) {
EditTextBuffer buffer("");
EXPECT_EQ(buffer.lines(), 0);
EXPECT_EQ(buffer.document_length(), 0);
buffer.RequestContent([](absl::string_view s) { //
EXPECT_TRUE(s.empty());
});
}
TEST(TextBufferTest, RequestParticularLine) {
EditTextBuffer buffer("foo\nbar\nbaz\n");
ASSERT_EQ(buffer.lines(), 3);
buffer.RequestLine(0, [](absl::string_view s) { //
EXPECT_EQ(std::string(s), "foo\n");
});
buffer.RequestLine(1, [](absl::string_view s) { //
EXPECT_EQ(std::string(s), "bar\n");
});
// Be graceful with out-of-range requests.
buffer.RequestLine(-1, [](absl::string_view s) { //
EXPECT_TRUE(s.empty());
});
buffer.RequestLine(100, [](absl::string_view s) { //
EXPECT_TRUE(s.empty());
});
}
TEST(TextBufferTest, RecreateFileWithAndWithoutNewlineAtEOF) {
static constexpr absl::string_view kBaseFile =
"Hello World\n"
"\n"
"Foo";
for (const absl::string_view append : {"", "\n", "\r\n"}) {
const std::string &content = absl::StrCat(kBaseFile, append);
EditTextBuffer buffer(content);
EXPECT_EQ(buffer.lines(), 3);
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ(std::string(s), content);
});
}
}
TEST(TextBufferTest, RecreateCRLFFiles) {
EditTextBuffer buffer("Foo\r\nBar\r\n");
EXPECT_EQ(buffer.lines(), 2);
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Foo\r\nBar\r\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplyFullContent) {
EditTextBuffer buffer("Foo\nBar\n");
const TextDocumentContentChangeEvent change = {
.range = {},
.has_range = false,
.text = "NewFile",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("NewFile", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_Insert) {
EditTextBuffer buffer("Hello World");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6},
.end = {0, 6},
},
.has_range = true,
.text = "brave ",
};
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.document_length(), 17);
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hello brave World", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLineWithUTF8Characters_Insert) {
EditTextBuffer buffer("Hëllö World");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6},
.end = {0, 6},
},
.has_range = true,
.text = "brave ",
};
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.document_length(), 19);
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hëllö brave World", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_InsertFromEmptyFile) {
EditTextBuffer buffer("");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 0},
.end = {0, 0},
},
.has_range = true,
.text = "New File!",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("New File!", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_Replace) {
EditTextBuffer buffer("Hello World\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6},
.end = {0, 11},
},
.has_range = true,
.text = "Planet",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hello Planet\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLineWihtUTF8Characters_Replace) {
EditTextBuffer buffer("Heizölrückstoßabdämpfung\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6},
.end = {0, 14},
},
.has_range = true,
.text = "brandgefahr",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Heizölbrandgefahrabdämpfung\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_ReplaceNotFirstLine) {
// Make sure we properly access the right line.
EditTextBuffer buffer("Hello World\nFoo\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {1, 0},
.end = {1, 3},
},
.has_range = true,
.text = "Bar",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hello World\nBar\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_Erase) {
EditTextBuffer buffer("Hello World\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 5},
.end = {0, 11},
},
.has_range = true,
.text = "",
};
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.document_length(), 6);
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("Hello\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplySingleLine_ReplaceCorrectOverlongEnd) {
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6}, .end = {0, 42}, // Too long end shall be trimmed
},
.has_range = true,
.text = "Planet",
};
{
EditTextBuffer buffer("Hello World\n");
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hello Planet\n", std::string(s));
});
}
{
EditTextBuffer buffer("Hello World");
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("Hello Planet", std::string(s));
});
}
}
TEST(TextBufferTest, ChangeApplyMultiLine_EraseBetweenLines) {
EditTextBuffer buffer("Hello\nWorld\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 2}, // From here to end of line
.end = {1, 0},
},
.has_range = true,
.text = "y ",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("Hey World\n", std::string(s));
});
EXPECT_EQ(buffer.document_length(), 10);
}
TEST(TextBufferTest, ChangeApplyMultiLineWithUTF8Characters_Modify) {
EditTextBuffer buffer("Heizölrück-\nstoßabdämpfung\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 6}, // From here to end of line
.end = {1, 4},
},
.has_range = true,
.text = "brand-\ngefahr",
};
EXPECT_TRUE(buffer.ApplyChange(change));
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("Heizölbrand-\ngefahrabdämpfung\n", std::string(s));
});
}
TEST(TextBufferTest, ChangeApplyMultiLine_InsertMoreLines) {
EditTextBuffer buffer("Hello\nbrave World\n");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 2}, // From here to end of line
.end = {1, 5},
},
.has_range = true,
.text = "y!\nThis will be a new line\nand more in this",
};
EXPECT_EQ(buffer.lines(), 2);
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.lines(), 3);
static constexpr absl::string_view kExpected =
"Hey!\nThis will be a new line\nand more in this World\n";
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ(kExpected, std::string(s));
});
EXPECT_EQ(buffer.document_length(), kExpected.length());
}
TEST(TextBufferTest, ChangeApplyMultiLine_InsertFromStart) {
EditTextBuffer buffer("");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {0, 0},
.end = {0, 0},
},
.has_range = true,
.text = "This is now\na multiline\nfile\n",
};
EXPECT_EQ(buffer.lines(), 0);
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.lines(), 3);
buffer.RequestContent([&](absl::string_view s) {
EXPECT_EQ("This is now\na multiline\nfile\n", std::string(s));
});
EXPECT_EQ(buffer.document_length(), change.text.length());
}
TEST(TextBufferTest, ChangeApplyMultiLine_RemoveLines) {
EditTextBuffer buffer("Foo\nBar\nBaz\nQuux");
const TextDocumentContentChangeEvent change = {
.range =
{
.start = {1, 0},
.end = {3, 0},
},
.has_range = true,
.text = "",
};
EXPECT_EQ(buffer.lines(), 4);
EXPECT_TRUE(buffer.ApplyChange(change));
EXPECT_EQ(buffer.lines(), 2);
buffer.RequestContent([&](absl::string_view s) { //
EXPECT_EQ("Foo\nQuux", std::string(s));
});
EXPECT_EQ(buffer.document_length(), 8);
}
TEST(BufferCollection, SimulateDocumentLifecycleThroughRPC) {
// Let's walk a BufferCollection through the lifecycle of a document
// by sending it the JSON RPC notifications for open, change and close.
//
// Inspect at each step that the content and number of documents is what
// we expect.
// Notifications don't send any responses; write function not relevant.
JsonRpcDispatcher rpc_dispatcher([](absl::string_view) {});
BufferCollection collection(&rpc_dispatcher);
EXPECT_EQ(collection.documents_open(), 0);
int64_t last_global_version = 0;
// Let's check that the callback is called on opening and contains
// the right content.
int change_callback_called = 0;
collection.SetChangeListener(
[&](const std::string &uri, const EditTextBuffer *buffer) {
EXPECT_EQ(uri, "file:///foo.cc");
buffer->RequestContent([](absl::string_view s) {
EXPECT_EQ(std::string(s), "Hello\nworld");
});
change_callback_called++;
});
// ------ Opening new document
// Simulate an incoming event from the client
rpc_dispatcher.DispatchMessage(R"({
"jsonrpc":"2.0",
"method":"textDocument/didOpen",
"params":{
"textDocument":{
"uri": "file:///foo.cc",
"text": "Hello\nworld",
"languageId": "cpp",
"version": 1
}
}})");
// We now expect one document to be open.
EXPECT_EQ(collection.documents_open(), 1);
EXPECT_EQ(change_callback_called, 1);
EXPECT_GT(collection.global_version(), last_global_version);
// Prepare for the next expected change
collection.SetChangeListener(
[&](const std::string &uri, const EditTextBuffer *buffer) {
EXPECT_EQ(uri, "file:///foo.cc");
buffer->RequestContent(
[](absl::string_view s) { EXPECT_EQ(std::string(s), "Hey"); });
change_callback_called++;
});
// ------ Editing document: receive content changes
rpc_dispatcher.DispatchMessage(R"({
"jsonrpc":"2.0",
"method":"textDocument/didChange",
"params":{
"textDocument": { "uri": "file:///foo.cc" },
"contentChanges": [ {"text":"Hey"} ]
}})");
EXPECT_EQ(change_callback_called, 2);
// Upcoming change will send us a nullptr buffer as it is removed.
collection.SetChangeListener(
[&](const std::string &uri, const EditTextBuffer *buffer) {
EXPECT_EQ(buffer, nullptr);
change_callback_called++;
});
// ------ Closing document
rpc_dispatcher.DispatchMessage(R"({
"jsonrpc":"2.0",
"method":"textDocument/didClose",
"params":{
"textDocument":{ "uri": "file:///foo.cc" }
}})");
// No document open anymore
EXPECT_EQ(collection.documents_open(), 0);
EXPECT_EQ(change_callback_called, 3);
}
} // namespace lsp
} // namespace verible