Support moving between arbitrary history nodes
This commit is contained in:
parent
b9c77e2f09
commit
3edd2c127c
132
src/buffer.cc
132
src/buffer.cc
|
@ -59,8 +59,8 @@ static void apply_options(OptionManager& options, const ParsedLines& parsed_line
|
||||||
options.get_local_option("BOM").set(parsed_lines.bom);
|
options.get_local_option("BOM").set(parsed_lines.bom);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::HistoryNode::HistoryNode(HistoryNode* parent)
|
Buffer::HistoryNode::HistoryNode(size_t id, HistoryNode* parent)
|
||||||
: parent(parent), timepoint{Clock::now()}
|
: id{id}, parent(parent), timepoint{Clock::now()}
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Buffer::Buffer(String name, Flags flags, StringView data,
|
Buffer::Buffer(String name, Flags flags, StringView data,
|
||||||
|
@ -69,7 +69,7 @@ Buffer::Buffer(String name, Flags flags, StringView data,
|
||||||
m_name{(flags & Flags::File) ? real_path(parse_filename(name)) : std::move(name)},
|
m_name{(flags & Flags::File) ? real_path(parse_filename(name)) : std::move(name)},
|
||||||
m_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name},
|
m_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name},
|
||||||
m_flags{flags | Flags::NoUndo},
|
m_flags{flags | Flags::NoUndo},
|
||||||
m_history{nullptr}, m_history_cursor{&m_history},
|
m_history{m_next_history_id++, nullptr}, m_history_cursor{&m_history},
|
||||||
m_last_save_history_cursor{&m_history},
|
m_last_save_history_cursor{&m_history},
|
||||||
m_fs_timestamp{fs_timestamp}
|
m_fs_timestamp{fs_timestamp}
|
||||||
{
|
{
|
||||||
|
@ -301,7 +301,7 @@ void Buffer::commit_undo_group()
|
||||||
if (m_current_undo_group.empty())
|
if (m_current_undo_group.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto* node = new HistoryNode{m_history_cursor.get()};
|
auto* node = new HistoryNode{m_next_history_id++, m_history_cursor.get()};
|
||||||
node->undo_group = std::move(m_current_undo_group);
|
node->undo_group = std::move(m_current_undo_group);
|
||||||
m_current_undo_group.clear();
|
m_current_undo_group.clear();
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ void Buffer::commit_undo_group()
|
||||||
m_history_cursor = node;
|
m_history_cursor = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Buffer::undo()
|
bool Buffer::undo() noexcept
|
||||||
{
|
{
|
||||||
commit_undo_group();
|
commit_undo_group();
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ bool Buffer::undo()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Buffer::redo()
|
bool Buffer::redo() noexcept
|
||||||
{
|
{
|
||||||
if (not m_history_cursor->redo_child)
|
if (not m_history_cursor->redo_child)
|
||||||
return false;
|
return false;
|
||||||
|
@ -338,6 +338,86 @@ bool Buffer::redo()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Buffer::move_to(HistoryNode* history_node) noexcept
|
||||||
|
{
|
||||||
|
commit_undo_group();
|
||||||
|
|
||||||
|
auto find_lowest_common_parent = [](HistoryNode* a, HistoryNode* b) {
|
||||||
|
auto depth_of = [](HistoryNode* node) {
|
||||||
|
size_t depth = 0;
|
||||||
|
for (; node->parent; node = node->parent.get())
|
||||||
|
++depth;
|
||||||
|
return depth;
|
||||||
|
};
|
||||||
|
auto depthA = depth_of(a), depthB = depth_of(b);
|
||||||
|
|
||||||
|
for (; depthA > depthB; --depthA)
|
||||||
|
a = a->parent.get();
|
||||||
|
for (; depthB > depthA; --depthB)
|
||||||
|
b = b->parent.get();
|
||||||
|
|
||||||
|
while (a != b)
|
||||||
|
{
|
||||||
|
a = a->parent.get();
|
||||||
|
b = b->parent.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
kak_assert(a == b and a != nullptr);
|
||||||
|
return a;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parent = find_lowest_common_parent(m_history_cursor.get(), history_node);
|
||||||
|
|
||||||
|
// undo up to common parent
|
||||||
|
for (auto it = m_history_cursor.get(); it != parent; it = it->parent.get())
|
||||||
|
{
|
||||||
|
for (const Modification& modification : it->undo_group | reverse())
|
||||||
|
apply_modification(modification.inverse());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void (*apply_from_parent)(Buffer&, HistoryNode*, HistoryNode*) =
|
||||||
|
[](Buffer& buffer, HistoryNode* parent, HistoryNode* node) {
|
||||||
|
if (node == parent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
apply_from_parent(buffer, parent, node->parent.get());
|
||||||
|
|
||||||
|
node->parent->redo_child = node;
|
||||||
|
|
||||||
|
for (const Modification& modification : node->undo_group)
|
||||||
|
buffer.apply_modification(modification);
|
||||||
|
};
|
||||||
|
|
||||||
|
apply_from_parent(*this, parent, history_node);
|
||||||
|
m_history_cursor = history_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
Buffer::HistoryNode* Buffer::find_history_node(HistoryNode* node, const Func& func)
|
||||||
|
{
|
||||||
|
if (func(node))
|
||||||
|
return node;
|
||||||
|
|
||||||
|
for (auto&& child : node->childs)
|
||||||
|
{
|
||||||
|
if (auto res = find_history_node(child.get(), func))
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Buffer::move_to(size_t history_id) noexcept
|
||||||
|
{
|
||||||
|
HistoryNode* target_node = find_history_node(&m_history, [history_id](HistoryNode* node) { return node->id == history_id; });
|
||||||
|
|
||||||
|
if (not target_node)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
move_to(target_node);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Buffer::check_invariant() const
|
void Buffer::check_invariant() const
|
||||||
{
|
{
|
||||||
#ifdef KAK_DEBUG
|
#ifdef KAK_DEBUG
|
||||||
|
@ -735,21 +815,43 @@ UnitTest test_buffer{[]()
|
||||||
UnitTest test_undo{[]()
|
UnitTest test_undo{[]()
|
||||||
{
|
{
|
||||||
Buffer buffer("test", Buffer::Flags::None, "allo ?\nmais que fais la police\n hein ?\n youpi\n");
|
Buffer buffer("test", Buffer::Flags::None, "allo ?\nmais que fais la police\n hein ?\n youpi\n");
|
||||||
auto pos = buffer.insert(buffer.end_coord(), "kanaky\n");
|
auto pos = buffer.insert(buffer.end_coord(), "kanaky\n"); // change 1
|
||||||
buffer.erase(pos, buffer.end_coord());
|
buffer.commit_undo_group();
|
||||||
buffer.insert(2_line, "tchou\n");
|
buffer.erase(pos, buffer.end_coord()); // change 2
|
||||||
buffer.insert(2_line, "mutch\n");
|
buffer.commit_undo_group();
|
||||||
buffer.erase({2, 1}, {2, 5});
|
buffer.insert(2_line, "tchou\n"); // change 3
|
||||||
buffer.replace(2_line, buffer.end_coord(), "youpi");
|
buffer.commit_undo_group();
|
||||||
|
buffer.undo();
|
||||||
|
buffer.insert(2_line, "mutch\n"); // change 4
|
||||||
|
buffer.commit_undo_group();
|
||||||
|
buffer.erase({2, 1}, {2, 5}); // change 5
|
||||||
|
buffer.commit_undo_group();
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
buffer.redo();
|
buffer.redo();
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
|
buffer.replace(2_line, buffer.end_coord(), "foo"); // change 6
|
||||||
|
buffer.commit_undo_group();
|
||||||
|
|
||||||
kak_assert((int)buffer.line_count() == 4);
|
kak_assert((int)buffer.line_count() == 3);
|
||||||
kak_assert(buffer[0_line] == "allo ?\n");
|
kak_assert(buffer[0_line] == "allo ?\n");
|
||||||
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
||||||
kak_assert(buffer[2_line] == " hein ?\n");
|
kak_assert(buffer[2_line] == "foo\n");
|
||||||
kak_assert(buffer[3_line] == " youpi\n");
|
|
||||||
|
buffer.move_to(3);
|
||||||
|
kak_assert((int)buffer.line_count() == 5);
|
||||||
|
kak_assert(buffer[0_line] == "allo ?\n");
|
||||||
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
||||||
|
kak_assert(buffer[2_line] == "tchou\n");
|
||||||
|
kak_assert(buffer[3_line] == " hein ?\n");
|
||||||
|
kak_assert(buffer[4_line] == " youpi\n");
|
||||||
|
|
||||||
|
buffer.move_to(4);
|
||||||
|
kak_assert((int)buffer.line_count() == 5);
|
||||||
|
kak_assert(buffer[0_line] == "allo ?\n");
|
||||||
|
kak_assert(buffer[1_line] == "mais que fais la police\n");
|
||||||
|
kak_assert(buffer[2_line] == "mutch\n");
|
||||||
|
kak_assert(buffer[3_line] == " hein ?\n");
|
||||||
|
kak_assert(buffer[4_line] == " youpi\n");
|
||||||
}};
|
}};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,9 @@ public:
|
||||||
void set_fs_timestamp(timespec ts);
|
void set_fs_timestamp(timespec ts);
|
||||||
|
|
||||||
void commit_undo_group();
|
void commit_undo_group();
|
||||||
bool undo();
|
bool undo() noexcept;
|
||||||
bool redo();
|
bool redo() noexcept;
|
||||||
|
bool move_to(size_t history_id) noexcept;
|
||||||
|
|
||||||
String string(ByteCoord begin, ByteCoord end) const;
|
String string(ByteCoord begin, ByteCoord end) const;
|
||||||
|
|
||||||
|
@ -245,8 +246,9 @@ private:
|
||||||
|
|
||||||
struct HistoryNode : SafeCountable, UseMemoryDomain<MemoryDomain::BufferMeta>
|
struct HistoryNode : SafeCountable, UseMemoryDomain<MemoryDomain::BufferMeta>
|
||||||
{
|
{
|
||||||
HistoryNode(HistoryNode* parent);
|
HistoryNode(size_t id, HistoryNode* parent);
|
||||||
|
|
||||||
|
size_t id;
|
||||||
SafePtr<HistoryNode> parent;
|
SafePtr<HistoryNode> parent;
|
||||||
UndoGroup undo_group;
|
UndoGroup undo_group;
|
||||||
Vector<std::unique_ptr<HistoryNode>, MemoryDomain::BufferMeta> childs;
|
Vector<std::unique_ptr<HistoryNode>, MemoryDomain::BufferMeta> childs;
|
||||||
|
@ -254,11 +256,16 @@ private:
|
||||||
TimePoint timepoint;
|
TimePoint timepoint;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t m_next_history_id = 0;
|
||||||
HistoryNode m_history;
|
HistoryNode m_history;
|
||||||
SafePtr<HistoryNode> m_history_cursor;
|
SafePtr<HistoryNode> m_history_cursor;
|
||||||
SafePtr<HistoryNode> m_last_save_history_cursor;
|
SafePtr<HistoryNode> m_last_save_history_cursor;
|
||||||
UndoGroup m_current_undo_group;
|
UndoGroup m_current_undo_group;
|
||||||
|
|
||||||
|
void move_to(HistoryNode* history_node) noexcept;
|
||||||
|
|
||||||
|
template<typename Func> HistoryNode* find_history_node(HistoryNode* node, const Func& func);
|
||||||
|
|
||||||
Vector<Change, MemoryDomain::BufferMeta> m_changes;
|
Vector<Change, MemoryDomain::BufferMeta> m_changes;
|
||||||
|
|
||||||
timespec m_fs_timestamp;
|
timespec m_fs_timestamp;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user