From 2296b43299d13cae6094f3e19e509428bc6be783 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 19 Jul 2016 22:03:47 +0100 Subject: [PATCH 1/8] Store buffer undo/redo information in a tree instead of a vector --- src/buffer.cc | 68 ++++++++++++++++++++++++++------------------------- src/buffer.hh | 18 ++++++++++---- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index 51be811f..87551c5d 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -59,15 +59,17 @@ 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) {} + Buffer::Buffer(String name, Flags flags, StringView data, timespec fs_timestamp) - : Scope(GlobalScope::instance()), - 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(), m_history_cursor(m_history.begin()), - m_last_save_undo_index(0), - m_fs_timestamp(fs_timestamp) + : Scope{GlobalScope::instance()}, + 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_last_save_history_cursor{&m_history}, + m_fs_timestamp{fs_timestamp} { ParsedLines parsed_lines = parse_lines(data); @@ -285,7 +287,7 @@ void Buffer::reload(StringView data, timespec fs_timestamp) apply_options(options(), parsed_lines); - m_last_save_undo_index = m_history_cursor - m_history.begin(); + m_last_save_history_cursor = m_history_cursor; m_fs_timestamp = fs_timestamp; } @@ -297,41 +299,40 @@ void Buffer::commit_undo_group() if (m_current_undo_group.empty()) return; - m_history.erase(m_history_cursor, m_history.end()); - - m_history.push_back(std::move(m_current_undo_group)); + auto* node = new HistoryNode{m_history_cursor.get()}; + node->undo_group = std::move(m_current_undo_group); m_current_undo_group.clear(); - m_history_cursor = m_history.end(); - if (m_history.size() < m_last_save_undo_index) - m_last_save_undo_index = -1; + m_history_cursor->childs.emplace_back(node); + m_history_cursor->redo_child = node; + m_history_cursor = node; } bool Buffer::undo() { commit_undo_group(); - if (m_history_cursor == m_history.begin()) + if (not m_history_cursor->parent) return false; - --m_history_cursor; - - for (const Modification& modification : *m_history_cursor | reverse()) + for (const Modification& modification : m_history_cursor->undo_group | reverse()) apply_modification(modification.inverse()); + + m_history_cursor = m_history_cursor->parent; return true; } bool Buffer::redo() { - if (m_history_cursor == m_history.end()) + if (not m_history_cursor->redo_child) return false; kak_assert(m_current_undo_group.empty()); - for (const Modification& modification : *m_history_cursor) - apply_modification(modification); + m_history_cursor = m_history_cursor->redo_child.get(); - ++m_history_cursor; + for (const Modification& modification : m_history_cursor->undo_group) + apply_modification(modification); return true; } @@ -505,8 +506,7 @@ ByteCoord Buffer::replace(ByteCoord begin, ByteCoord end, StringView content) bool Buffer::is_modified() const { - size_t history_cursor_index = m_history_cursor - m_history.begin(); - return m_last_save_undo_index != history_cursor_index + return m_history_cursor != m_last_save_history_cursor or not m_current_undo_group.empty(); } @@ -516,9 +516,7 @@ void Buffer::notify_saved() commit_undo_group(); m_flags &= ~Flags::New; - size_t history_cursor_index = m_history_cursor - m_history.begin(); - if (m_last_save_undo_index != history_cursor_index) - m_last_save_undo_index = history_cursor_index; + m_last_save_history_cursor = m_history_cursor; m_fs_timestamp = get_fs_timestamp(m_name); } @@ -625,9 +623,9 @@ void Buffer::run_hook_in_own_context(StringView hook_name, StringView param) ByteCoord Buffer::last_modification_coord() const { - if (m_history.empty()) + if (m_history_cursor.get() == &m_history) return {}; - return m_history.back().back().coord; + return m_history_cursor->undo_group.back().coord; } String Buffer::debug_description() const @@ -636,10 +634,14 @@ String Buffer::debug_description() const for (auto& line : m_lines) content_size += (int)line->strview().length(); - size_t additional_size = 0; - for (auto& undo_group : m_history) - additional_size += undo_group.size() * sizeof(Modification); - additional_size += m_changes.size() * sizeof(Change); + static size_t (*count_mem)(const HistoryNode&) = [](const HistoryNode& node) { + size_t size = node.undo_group.size() * sizeof(Modification); + for (auto& child : node.childs) + size += count_mem(*child); + return size; + }; + const size_t additional_size = count_mem(m_history) + + m_changes.size() * sizeof(Change); return format("{}\nFlags: {}{}{}{}\nUsed mem: content={} additional={}\n", display_name(), diff --git a/src/buffer.hh b/src/buffer.hh index e84c14e0..1ba21b73 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -241,13 +241,21 @@ private: Flags m_flags; using UndoGroup = Vector; - using History = Vector; - History m_history; - History::iterator m_history_cursor; - UndoGroup m_current_undo_group; + struct HistoryNode : SafeCountable + { + HistoryNode(HistoryNode* parent); - size_t m_last_save_undo_index; + SafePtr parent; + UndoGroup undo_group; + Vector> childs; + SafePtr redo_child; + }; + + HistoryNode m_history; + SafePtr m_history_cursor; + SafePtr m_last_save_history_cursor; + UndoGroup m_current_undo_group; Vector m_changes; From e391f93a9e1dbf7e087a34bd0b040ef348cce506 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 08:39:36 +0100 Subject: [PATCH 2/8] Set memory domains for Buffer::HistoryNode --- src/buffer.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer.hh b/src/buffer.hh index 1ba21b73..2bc1e098 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -242,13 +242,13 @@ private: using UndoGroup = Vector; - struct HistoryNode : SafeCountable + struct HistoryNode : SafeCountable, UseMemoryDomain { HistoryNode(HistoryNode* parent); SafePtr parent; UndoGroup undo_group; - Vector> childs; + Vector, MemoryDomain::BufferMeta> childs; SafePtr redo_child; }; From 46a15534c5d012908eee4975a21dd0d53179b2b3 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 08:49:04 +0100 Subject: [PATCH 3/8] Introduce chrono.hh --- src/clock.hh | 14 ++++++++++++++ src/event_manager.hh | 5 +---- src/hook_manager.cc | 6 +----- src/shell_manager.cc | 14 ++++++-------- src/window.cc | 9 ++++----- 5 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 src/clock.hh diff --git a/src/clock.hh b/src/clock.hh new file mode 100644 index 00000000..b4138de0 --- /dev/null +++ b/src/clock.hh @@ -0,0 +1,14 @@ +#ifndef clock_hh_INCLUDED +#define clock_hh_INCLUDED + +#include + +namespace Kakoune +{ + +using Clock = std::chrono::steady_clock; +using TimePoint = Clock::time_point; + +} + +#endif // clock_hh_INCLUDED diff --git a/src/event_manager.hh b/src/event_manager.hh index 81bfc508..3c1567c9 100644 --- a/src/event_manager.hh +++ b/src/event_manager.hh @@ -1,11 +1,11 @@ #ifndef event_manager_hh_INCLUDED #define event_manager_hh_INCLUDED +#include "clock.hh" #include "utils.hh" #include "flags.hh" #include "vector.hh" -#include #include #include @@ -41,9 +41,6 @@ private: Callback m_callback; }; -using Clock = std::chrono::steady_clock; -using TimePoint = Clock::time_point; - class Timer { public: diff --git a/src/hook_manager.cc b/src/hook_manager.cc index 30f90ae3..be940482 100644 --- a/src/hook_manager.cc +++ b/src/hook_manager.cc @@ -1,5 +1,6 @@ #include "hook_manager.hh" +#include "clock.hh" #include "containers.hh" #include "context.hh" #include "buffer_utils.hh" @@ -7,8 +8,6 @@ #include "face_registry.hh" #include "regex.hh" -#include - namespace Kakoune { @@ -61,9 +60,6 @@ void HookManager::run_hook(StringView hook_name, m_running_hooks.emplace_back(hook_name, param); auto pop_running_hook = on_scope_end([this]{ m_running_hooks.pop_back(); }); - using Clock = std::chrono::steady_clock; - using TimePoint = Clock::time_point; - const DebugFlags debug_flags = context.options()["debug"].get(); const bool profile = debug_flags & DebugFlags::Profile; auto start_time = profile ? Clock::now() : TimePoint{}; diff --git a/src/shell_manager.cc b/src/shell_manager.cc index e0345e36..01ff1db2 100644 --- a/src/shell_manager.cc +++ b/src/shell_manager.cc @@ -1,12 +1,11 @@ #include "shell_manager.hh" +#include "clock.hh" #include "context.hh" #include "buffer_utils.hh" #include "event_manager.hh" #include "file.hh" -#include - #include #include #include @@ -115,18 +114,16 @@ std::pair ShellManager::eval( StringView cmdline, const Context& context, StringView input, Flags flags, const ShellContext& shell_context) { - using namespace std::chrono; - const DebugFlags debug_flags = context.options()["debug"].get(); const bool profile = debug_flags & DebugFlags::Profile; if (debug_flags & DebugFlags::Shell) write_to_debug_buffer(format("shell:\n{}\n----\n", cmdline)); - auto start_time = profile ? steady_clock::now() : steady_clock::time_point{}; + auto start_time = profile ? Clock::now() : Clock::time_point{}; auto kak_env = generate_env(cmdline, context, shell_context); - auto spawn_time = profile ? steady_clock::now() : steady_clock::time_point{}; + auto spawn_time = profile ? Clock::now() : Clock::time_point{}; Pipe child_stdin{not input.empty()}, child_stdout, child_stderr; pid_t pid = spawn_shell(cmdline, shell_context.params, kak_env, @@ -153,7 +150,7 @@ std::pair ShellManager::eval( write(child_stdin.write_fd(), input); child_stdin.close_write_fd(); - auto wait_time = profile ? steady_clock::now() : steady_clock::time_point{}; + auto wait_time = profile ? Clock::now() : Clock::time_point{}; struct PipeReader : FDWatcher { @@ -203,7 +200,8 @@ std::pair ShellManager::eval( if (profile) { - auto end_time = steady_clock::now(); + using namespace std::chrono; + auto end_time = Clock::now(); auto full = duration_cast(end_time - start_time); auto spawn = duration_cast(wait_time - spawn_time); auto wait = duration_cast(end_time - wait_time); diff --git a/src/window.cc b/src/window.cc index de3f4ea4..efffe1e1 100644 --- a/src/window.cc +++ b/src/window.cc @@ -1,6 +1,7 @@ #include "window.hh" #include "assert.hh" +#include "clock.hh" #include "context.hh" #include "highlighter.hh" #include "hook_manager.hh" @@ -9,7 +10,6 @@ #include "buffer_utils.hh" #include -#include #include namespace Kakoune @@ -109,12 +109,10 @@ bool Window::needs_redraw(const Context& context) const const DisplayBuffer& Window::update_display_buffer(const Context& context) { - using namespace std::chrono; - const bool profile = context.options()["debug"].get() & DebugFlags::Profile; - auto start_time = profile ? steady_clock::now() : steady_clock::time_point{}; + auto start_time = profile ? Clock::now() : Clock::time_point{}; DisplayBuffer::LineList& lines = m_display_buffer.lines(); lines.clear(); @@ -147,7 +145,8 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context) if (profile and not (buffer().flags() & Buffer::Flags::Debug)) { - auto duration = duration_cast(steady_clock::now() - start_time); + using namespace std::chrono; + auto duration = duration_cast(Clock::now() - start_time); write_to_debug_buffer(format("window display update for '{}' took {} ms", buffer().display_name(), (size_t)duration.count())); } From b9c77e2f09f34985aec83b6f5e5e9dc4b942af2f Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 08:50:38 +0100 Subject: [PATCH 4/8] Store creation timepoint in HistoryNode --- src/buffer.cc | 4 +++- src/buffer.hh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/buffer.cc b/src/buffer.cc index 87551c5d..d0a17f74 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -59,7 +59,9 @@ 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) {} +Buffer::HistoryNode::HistoryNode(HistoryNode* parent) + : parent(parent), timepoint{Clock::now()} +{} Buffer::Buffer(String name, Flags flags, StringView data, timespec fs_timestamp) diff --git a/src/buffer.hh b/src/buffer.hh index 2bc1e098..cd136476 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -1,6 +1,7 @@ #ifndef buffer_hh_INCLUDED #define buffer_hh_INCLUDED +#include "clock.hh" #include "coord.hh" #include "flags.hh" #include "safe_ptr.hh" @@ -250,6 +251,7 @@ private: UndoGroup undo_group; Vector, MemoryDomain::BufferMeta> childs; SafePtr redo_child; + TimePoint timepoint; }; HistoryNode m_history; From 3edd2c127c9d1754a487d76de38ec9b2c690ff7c Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 09:41:45 +0100 Subject: [PATCH 5/8] 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 d0a17f74..08b857ce 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 cd136476..be510a79 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; From 03a4b3c73ff8cf7d37387df8bd0fcf9e895b59e0 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 20:20:03 +0100 Subject: [PATCH 6/8] Support counts for undo/redo --- src/buffer.cc | 27 +++++++++++++++++---------- src/buffer.hh | 4 ++-- src/normal.cc | 15 ++++++--------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/buffer.cc b/src/buffer.cc index 08b857ce..028f9516 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -310,31 +310,38 @@ void Buffer::commit_undo_group() m_history_cursor = node; } -bool Buffer::undo() noexcept +bool Buffer::undo(size_t count) noexcept { commit_undo_group(); if (not m_history_cursor->parent) return false; - for (const Modification& modification : m_history_cursor->undo_group | reverse()) - apply_modification(modification.inverse()); + while (count-- and m_history_cursor->parent) + { + for (const Modification& modification : m_history_cursor->undo_group | reverse()) + apply_modification(modification.inverse()); + + m_history_cursor = m_history_cursor->parent; + } - m_history_cursor = m_history_cursor->parent; return true; } -bool Buffer::redo() noexcept +bool Buffer::redo(size_t count) noexcept { if (not m_history_cursor->redo_child) return false; kak_assert(m_current_undo_group.empty()); - m_history_cursor = m_history_cursor->redo_child.get(); + while (count-- and m_history_cursor->redo_child) + { + m_history_cursor = m_history_cursor->redo_child.get(); - for (const Modification& modification : m_history_cursor->undo_group) - apply_modification(modification); + for (const Modification& modification : m_history_cursor->undo_group) + apply_modification(modification); + } return true; } @@ -826,8 +833,8 @@ UnitTest test_undo{[]() buffer.commit_undo_group(); buffer.erase({2, 1}, {2, 5}); // change 5 buffer.commit_undo_group(); - buffer.undo(); - buffer.redo(); + buffer.undo(2); + buffer.redo(2); buffer.undo(); buffer.replace(2_line, buffer.end_coord(), "foo"); // change 6 buffer.commit_undo_group(); diff --git a/src/buffer.hh b/src/buffer.hh index be510a79..4cd99c8d 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -132,8 +132,8 @@ public: void set_fs_timestamp(timespec ts); void commit_undo_group(); - bool undo() noexcept; - bool redo() noexcept; + bool undo(size_t count = 1) noexcept; + bool redo(size_t count = 1) noexcept; bool move_to(size_t history_id) noexcept; String string(ByteCoord begin, ByteCoord end) const; diff --git a/src/normal.cc b/src/normal.cc index 940e11d8..316fc5e2 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1414,37 +1414,34 @@ void restore_selections(Context& context, NormalParams params) context.print_status({format("Restored selections from register '{}'", reg), get_face("Information")}); } -void undo(Context& context, NormalParams) +void undo(Context& context, NormalParams params) { Buffer& buffer = context.buffer(); size_t timestamp = buffer.timestamp(); - bool res = buffer.undo(); - if (res) + if (buffer.undo(std::max(1, params.count))) { auto ranges = compute_modified_ranges(buffer, timestamp); if (not ranges.empty()) context.selections_write_only() = std::move(ranges); context.selections().avoid_eol(); } - else if (not res) + else context.print_status({ "nothing left to undo", get_face("Information") }); } -void redo(Context& context, NormalParams) +void redo(Context& context, NormalParams params) { using namespace std::placeholders; Buffer& buffer = context.buffer(); size_t timestamp = buffer.timestamp(); - bool res = buffer.redo(); - if (res) + if (buffer.redo(std::max(1, params.count))) { auto ranges = compute_modified_ranges(buffer, timestamp); if (not ranges.empty()) context.selections_write_only() = std::move(ranges); context.selections().avoid_eol(); } - - else if (not res) + else context.print_status({ "nothing left to redo", get_face("Information") }); } From 087a17eb24059da1a98596bb5c8e9064a72776f4 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 20 Jul 2016 20:29:45 +0100 Subject: [PATCH 7/8] Support for going backward/forward in buffer history with / --- src/buffer.cc | 5 +++++ src/buffer.hh | 1 + src/normal.cc | 22 +++++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/buffer.cc b/src/buffer.cc index 028f9516..ec970676 100644 --- a/src/buffer.cc +++ b/src/buffer.cc @@ -425,6 +425,11 @@ bool Buffer::move_to(size_t history_id) noexcept return true; } +size_t Buffer::current_history_id() const noexcept +{ + return m_history_cursor->id; +} + void Buffer::check_invariant() const { #ifdef KAK_DEBUG diff --git a/src/buffer.hh b/src/buffer.hh index 4cd99c8d..845d04b1 100644 --- a/src/buffer.hh +++ b/src/buffer.hh @@ -135,6 +135,7 @@ public: bool undo(size_t count = 1) noexcept; bool redo(size_t count = 1) noexcept; bool move_to(size_t history_id) noexcept; + size_t current_history_id() const noexcept; String string(ByteCoord begin, ByteCoord end) const; diff --git a/src/normal.cc b/src/normal.cc index 316fc5e2..544fa08f 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1431,7 +1431,6 @@ void undo(Context& context, NormalParams params) void redo(Context& context, NormalParams params) { - using namespace std::placeholders; Buffer& buffer = context.buffer(); size_t timestamp = buffer.timestamp(); if (buffer.redo(std::max(1, params.count))) @@ -1445,6 +1444,25 @@ void redo(Context& context, NormalParams params) context.print_status({ "nothing left to redo", get_face("Information") }); } +template +void move_in_history(Context& context, NormalParams params) +{ + Buffer& buffer = context.buffer(); + size_t timestamp = buffer.timestamp(); + const size_t count = (size_t)std::max(1, params.count); + const size_t history_id = buffer.current_history_id() + + (direction == Direction::Forward ? count : -count); + if (buffer.move_to(history_id)) + { + auto ranges = compute_modified_ranges(buffer, timestamp); + if (not ranges.empty()) + context.selections_write_only() = std::move(ranges); + context.selections().avoid_eol(); + } + else + context.print_status({ "nothing left to redo", get_face("Information") }); +} + void exec_user_mappings(Context& context, NormalParams params) { on_next_key_with_autoinfo(context, KeymapMode::None, @@ -1679,6 +1697,8 @@ static NormalCmdDesc cmds[] = { 'u', "undo", undo }, { 'U', "redo", redo }, + { alt('u'), "move backward in history", move_in_history }, + { alt('U'), "move forward in history", move_in_history }, { alt('i'), "select inner object", select_object }, { alt('a'), "select whole object", select_object }, From 78fc88ae6dd472cfa575cb762c31390a480b6ca4 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sun, 24 Jul 2016 22:53:44 +0100 Subject: [PATCH 8/8] Display some information when moving in history --- src/normal.cc | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/normal.cc b/src/normal.cc index 544fa08f..50f7ca61 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1449,18 +1449,22 @@ void move_in_history(Context& context, NormalParams params) { Buffer& buffer = context.buffer(); size_t timestamp = buffer.timestamp(); - const size_t count = (size_t)std::max(1, params.count); - const size_t history_id = buffer.current_history_id() + + const int count = std::max(1, params.count); + const int history_id = (int)buffer.current_history_id() + (direction == Direction::Forward ? count : -count); - if (buffer.move_to(history_id)) + if (buffer.move_to((size_t)history_id)) { auto ranges = compute_modified_ranges(buffer, timestamp); if (not ranges.empty()) context.selections_write_only() = std::move(ranges); context.selections().avoid_eol(); + + context.print_status({ format("moved to change #{}", history_id), + get_face("Information") }); } else - context.print_status({ "nothing left to redo", get_face("Information") }); + context.print_status({ format("no such change: #{}", history_id), + get_face("Information") }); } void exec_user_mappings(Context& context, NormalParams params)