Add swap journaling and group undo/redo with extensive tests.
- Introduced SwapManager for sidecar journaling of buffer mutations, with a safe recovery mechanism. - Added group undo/redo functionality, allowing atomic grouping of related edits. - Implemented `SwapRecorder` and integrated it as a callback interface for mutations. - Added unit tests for swap journaling (save/load/replay) and undo grouping. - Refactored undo to support group tracking and ID management. - Updated CMake to include the new tests and swap journaling logic.
This commit is contained in:
104
tests/test_swap_recorder.cc
Normal file
104
tests/test_swap_recorder.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "Test.h"
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "SwapRecorder.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
struct SwapEvent {
|
||||
enum class Type {
|
||||
Insert,
|
||||
Delete,
|
||||
};
|
||||
|
||||
Type type;
|
||||
int row;
|
||||
int col;
|
||||
std::string bytes;
|
||||
std::size_t len = 0;
|
||||
};
|
||||
|
||||
class FakeSwapRecorder final : public kte::SwapRecorder {
|
||||
public:
|
||||
std::vector<SwapEvent> events;
|
||||
|
||||
|
||||
void OnInsert(int row, int col, std::string_view bytes) override
|
||||
{
|
||||
SwapEvent e;
|
||||
e.type = SwapEvent::Type::Insert;
|
||||
e.row = row;
|
||||
e.col = col;
|
||||
e.bytes = std::string(bytes);
|
||||
e.len = 0;
|
||||
events.push_back(std::move(e));
|
||||
}
|
||||
|
||||
|
||||
void OnDelete(int row, int col, std::size_t len) override
|
||||
{
|
||||
SwapEvent e;
|
||||
e.type = SwapEvent::Type::Delete;
|
||||
e.row = row;
|
||||
e.col = col;
|
||||
e.len = len;
|
||||
events.push_back(std::move(e));
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
TEST (SwapRecorder_InsertABC)
|
||||
{
|
||||
Buffer b;
|
||||
FakeSwapRecorder rec;
|
||||
b.SetSwapRecorder(&rec);
|
||||
|
||||
b.insert_text(0, 0, std::string_view("abc"));
|
||||
|
||||
ASSERT_EQ(rec.events.size(), (std::size_t) 1);
|
||||
ASSERT_TRUE(rec.events[0].type == SwapEvent::Type::Insert);
|
||||
ASSERT_EQ(rec.events[0].row, 0);
|
||||
ASSERT_EQ(rec.events[0].col, 0);
|
||||
ASSERT_EQ(rec.events[0].bytes, std::string("abc"));
|
||||
}
|
||||
|
||||
|
||||
TEST (SwapRecorder_InsertNewline)
|
||||
{
|
||||
Buffer b;
|
||||
FakeSwapRecorder rec;
|
||||
b.SetSwapRecorder(&rec);
|
||||
|
||||
b.split_line(0, 0);
|
||||
|
||||
ASSERT_EQ(rec.events.size(), (std::size_t) 1);
|
||||
ASSERT_TRUE(rec.events[0].type == SwapEvent::Type::Insert);
|
||||
ASSERT_EQ(rec.events[0].row, 0);
|
||||
ASSERT_EQ(rec.events[0].col, 0);
|
||||
ASSERT_EQ(rec.events[0].bytes, std::string("\n"));
|
||||
}
|
||||
|
||||
|
||||
TEST (SwapRecorder_DeleteSpanningNewline)
|
||||
{
|
||||
Buffer b;
|
||||
// Prepare content without a recorder (should be no-op)
|
||||
b.insert_text(0, 0, std::string_view("ab"));
|
||||
b.split_line(0, 2);
|
||||
b.insert_text(1, 0, std::string_view("cd"));
|
||||
|
||||
FakeSwapRecorder rec;
|
||||
b.SetSwapRecorder(&rec);
|
||||
|
||||
// Delete "b\n c" (3 bytes) starting at row 0, col 1.
|
||||
b.delete_text(0, 1, 3);
|
||||
|
||||
ASSERT_EQ(rec.events.size(), (std::size_t) 1);
|
||||
ASSERT_TRUE(rec.events[0].type == SwapEvent::Type::Delete);
|
||||
ASSERT_EQ(rec.events[0].row, 0);
|
||||
ASSERT_EQ(rec.events[0].col, 1);
|
||||
ASSERT_EQ(rec.events[0].len, (std::size_t) 3);
|
||||
}
|
||||
Reference in New Issue
Block a user