From 3edd2c127c9d1754a487d76de38ec9b2c690ff7c Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 09:41:45 +0100 Subject: [PATCH] Support moving between arbitrary history nodes --- src/buffer.cc | 132 ++++++++++++++++++++++++++++++++++++++++++++------ src/buffer.hh | 13 +++-- 2 files changed, 127 insertions(+), 18 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index d0a17f74c..08b857ced 100644 --- a/src/buffer.cc +++ b/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); } -Buffer::HistoryNode::HistoryNode(HistoryNode* parent) - : parent(parent), timepoint{Clock::now()} +Buffer::HistoryNode::HistoryNode(size_t id, HistoryNode* parent) + : id{id}, parent(parent), timepoint{Clock::now()} {} 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_display_name{(flags & Flags::File) ? compact_path(m_name) : m_name}, 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_fs_timestamp{fs_timestamp} { @@ -301,7 +301,7 @@ void Buffer::commit_undo_group() if (m_current_undo_group.empty()) 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); m_current_undo_group.clear(); @@ -310,7 +310,7 @@ void Buffer::commit_undo_group() m_history_cursor = node; } -bool Buffer::undo() +bool Buffer::undo() noexcept { commit_undo_group(); @@ -324,7 +324,7 @@ bool Buffer::undo() return true; } -bool Buffer::redo() +bool Buffer::redo() noexcept { if (not m_history_cursor->redo_child) return false; @@ -338,6 +338,86 @@ bool Buffer::redo() 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 +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 { #ifdef KAK_DEBUG @@ -735,21 +815,43 @@ UnitTest test_buffer{[]() UnitTest test_undo{[]() { 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"); - buffer.erase(pos, buffer.end_coord()); - buffer.insert(2_line, "tchou\n"); - buffer.insert(2_line, "mutch\n"); - buffer.erase({2, 1}, {2, 5}); - buffer.replace(2_line, buffer.end_coord(), "youpi"); + auto pos = buffer.insert(buffer.end_coord(), "kanaky\n"); // change 1 + buffer.commit_undo_group(); + buffer.erase(pos, buffer.end_coord()); // change 2 + buffer.commit_undo_group(); + buffer.insert(2_line, "tchou\n"); // change 3 + 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.redo(); 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[1_line] == "mais que fais la police\n"); - kak_assert(buffer[2_line] == " hein ?\n"); - kak_assert(buffer[3_line] == " youpi\n"); + kak_assert(buffer[2_line] == "foo\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"); }}; } diff --git a/src/buffer.hh b/src/buffer.hh index cd1364767..be510a79f 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -132,8 +132,9 @@ public: void set_fs_timestamp(timespec ts); void commit_undo_group(); - bool undo(); - bool redo(); + bool undo() noexcept; + bool redo() noexcept; + bool move_to(size_t history_id) noexcept; String string(ByteCoord begin, ByteCoord end) const; @@ -245,8 +246,9 @@ private: struct HistoryNode : SafeCountable, UseMemoryDomain { - HistoryNode(HistoryNode* parent); + HistoryNode(size_t id, HistoryNode* parent); + size_t id; SafePtr parent; UndoGroup undo_group; Vector, MemoryDomain::BufferMeta> childs; @@ -254,11 +256,16 @@ private: TimePoint timepoint; }; + size_t m_next_history_id = 0; HistoryNode m_history; SafePtr m_history_cursor; SafePtr m_last_save_history_cursor; UndoGroup m_current_undo_group; + void move_to(HistoryNode* history_node) noexcept; + + template HistoryNode* find_history_node(HistoryNode* node, const Func& func); + Vector m_changes; timespec m_fs_timestamp;