Skip to content

Commit

Permalink
Allow to redo old branches from selection history
Browse files Browse the repository at this point in the history
Today, selection undo and jump list commands only travel the latest
branch of the selection history. If I undo a selection change and then
make another change, the undone change cannot be redone - it lives
on an old branch of the selection history.  Let's make it possible
to revisit such selections.

There are a couple of ways to implement this.  This version allows
to toggle between redo branches whenever there is more than one.
Hopefully this is not too hard to use.

A previous approach implemented a behavior equivalent to
mawww#4777, but that turns out to
be clunky for selection undo because when I use <c-o>, I typically
only want to travel the active branch and not revisit any abandoned
branches.

Another approach would be to mimic <a-u>.  This week, I finally
realized how that command works: it simply decrements the index of the
current history item. Since the history tree is stored in a vector,
this allows to visit any state. However this also means that
it includes weird jumps between branches in the history.
I somehow thought that it only jumps between the leaf nodes..
Anyway, I find this a bit unintuitive.
  • Loading branch information
krobelus committed Dec 26, 2022
1 parent 40e5dcc commit 91171fa
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 11 deletions.
11 changes: 11 additions & 0 deletions doc/pages/keys.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,17 @@ Searches use the */* register by default (See <<registers#,`:doc registers`>>)
*<c-k>*::
redo last selection change

When there are multiple selection changes to redo (for example after
`j<c-h>l<c-h>`), the latest one will be chosen by default. Whenever a
selection undo command visits such a junction point, it will show the active
redo branch in the status line. The active branch can be toggled using:

*<c-j>*::
previous selection history branch

*<c-J>*::
next selection history branch

Some commands, like the goto commands, buffer switch or search commands,
mark the previous selections as jump target. It is possible to undo selection
changes until a jump target:
Expand Down
73 changes: 63 additions & 10 deletions src/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,21 @@ void Context::SelectionHistory::end_edition()
m_staging.reset();
}

template<bool did_jump>
void Context::SelectionHistory::print_status()
{
const auto& node = current_history_node();
String alternative_branch_info;
if (node.redo_children.size() > 1)
alternative_branch_info = format(" (selected redo branch: {}/{})",
node.redo_children_index,
node.redo_children.size() - 1);
auto string = format("{} #{}{}",
did_jump ? "jumped to" : "selection",
(size_t)m_history_id, alternative_branch_info);
m_context.print_status({ std::move(string), m_context.faces()["Information"] });
}

template<Direction direction, bool to_jump>
void Context::SelectionHistory::undo()
{
Expand All @@ -235,21 +250,39 @@ void Context::SelectionHistory::undo()
HistoryId current = m_history_id;
while (true)
{
auto& current_node = history_node(current);
if constexpr (backward)
next = history_node(current).parent;
{
next = current_node.parent;
}
else
next = history_node(current).redo_child;
{
if (current_node.redo_children_index == -1 or
current_node.redo_children_index == current_node.redo_children.size())
next = HistoryId::Invalid;
else
next = current_node.redo_children.at(current_node.redo_children_index);
}
if (next == HistoryId::Invalid)
throw runtime_error(to_jump ? "no previous jump" : "no selection change to undo");
if constexpr (backward)
history_node(next).redo_child = current;
{
auto& next_node = history_node(next);
if (auto it = find(next_node.redo_children, current); it != next_node.redo_children.end())
{
next_node.redo_children_index = std::distance(next_node.redo_children.begin(), it);
}
else
{
next_node.redo_children_index = next_node.redo_children.size();
next_node.redo_children.push_back(current);
}
}
if (to_jump and not history_node(next).is_jump)
current = next;
else
break;
}
if (next == HistoryId::Invalid)
throw runtime_error(backward ? "no selection change to undo" : "no selection change to redo");
auto select_next = [&, next] {
m_history_id = next;
m_staging = current_history_node();
Expand All @@ -261,9 +294,20 @@ void Context::SelectionHistory::undo()
m_context.change_buffer(destination_buffer, false, { std::move(select_next) });
}
while (selections() == old_selections);
m_context.print_status({ format("jumped to #{} ({})",
(size_t)m_history_id, m_history.size() - 1),
m_context.faces()["Information"] });
print_status<true>();
}

template<Direction direction>
void Context::SelectionHistory::toggle_branch(size_t count)
{
auto& node = current_history_node();
if (node.redo_children.size() <= 1)
throw runtime_error("no alternative selection history branch");
node.redo_children_index += direction * count;
node.redo_children_index %= node.redo_children.size();
if (node.redo_children_index < 0)
node.redo_children_index += node.redo_children.size();
print_status<false>();
}

void Context::SelectionHistory::forget_buffer(Buffer& buffer)
Expand Down Expand Up @@ -294,13 +338,14 @@ void Context::SelectionHistory::forget_buffer(Buffer& buffer)
for (auto& node : m_history)
{
node.parent = new_id(node.parent);
node.redo_child = new_id(node.redo_child);
for (auto& redo_child : node.redo_children)
redo_child = new_id(redo_child);
}
m_history_id = new_id(m_history_id);
if (m_staging)
{
m_staging->parent = new_id(m_staging->parent);
kak_assert(m_staging->redo_child == HistoryId::Invalid);
kak_assert(m_staging->redo_children.empty());
}
kak_assert(m_history_id != HistoryId::Invalid or m_staging);
}
Expand Down Expand Up @@ -387,6 +432,14 @@ template void Context::undo_selection_change<Backward, false>();
template void Context::undo_selection_change<Forward, true>();
template void Context::undo_selection_change<Forward, false>();

template<Direction direction>
void Context::toggle_selection_history_branch(size_t count)
{
m_selection_history.toggle_branch<direction>(count);
}
template void Context::toggle_selection_history_branch<Backward>(size_t);
template void Context::toggle_selection_history_branch<Forward>(size_t);

SelectionList& Context::selections_write_only()
{
return selections(false);
Expand Down
9 changes: 8 additions & 1 deletion src/context.hh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ public:
void end_selection_edition() { m_selection_history.end_edition(); }
template<Direction direction, bool to_jump>
void undo_selection_change();
template<Direction direction>
void toggle_selection_history_branch(size_t count);

void change_buffer(Buffer& buffer, bool push_jump = false, Optional<FunctionRef<void()>> set_selection = {});
void forget_buffer(Buffer& buffer);
Expand Down Expand Up @@ -182,6 +184,8 @@ private:

template<Direction direction, bool to_jump>
void undo();
template<Direction direction>
void toggle_branch(size_t count);
void forget_buffer(Buffer& buffer);
private:
struct HistoryNode
Expand All @@ -190,14 +194,17 @@ private:

SelectionList selections;
HistoryId parent;
HistoryId redo_child = HistoryId::Invalid;
Vector<HistoryId, MemoryDomain::Selections> redo_children;
int redo_children_index = -1;
bool is_jump = false;
};

HistoryId next_history_id() const noexcept { return (HistoryId)m_history.size(); }
HistoryNode& history_node(HistoryId id) { return m_history[(size_t)id]; }
const HistoryNode& history_node(HistoryId id) const { return m_history[(size_t)id]; }
HistoryNode& current_history_node() { kak_assert((size_t)m_history_id < m_history.size()); return m_history[(size_t)m_history_id]; }
template<bool did_jump>
void print_status();

Context& m_context;
Vector<HistoryNode> m_history;
Expand Down
9 changes: 9 additions & 0 deletions src/normal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2045,6 +2045,13 @@ void undo_selection_change(Context& context, NormalParams params)
context.undo_selection_change<direction, to_jump>();
}

template<Direction direction>
void toggle_selection_history_branch(Context& context, NormalParams params)
{
int count = std::max(1, params.count);
context.toggle_selection_history_branch<direction>(count);
}

void exec_user_mappings(Context& context, NormalParams params)
{
on_next_key_with_autoinfo(context, "user-mapping", KeymapMode::None,
Expand Down Expand Up @@ -2372,6 +2379,8 @@ static constexpr HashMap<Key, NormalCmd, MemoryDomain::Undefined, KeymapBackend>

{ {ctrl('h')}, {"undo selection change", undo_selection_change<Backward, false>} },
{ {ctrl('k')}, {"redo selection change", undo_selection_change<Forward, false>} },
{ {ctrl('j')}, {"previous selection history branch", toggle_selection_history_branch<Backward>} },
{ {ctrl('J')}, {"next selection history branch", toggle_selection_history_branch<Forward>} },

{ {alt('i')}, {"select inner object", select_object<ObjectFlags::ToBegin | ObjectFlags::ToEnd | ObjectFlags::Inner>} },
{ {alt('a')}, {"select whole object", select_object<ObjectFlags::ToBegin | ObjectFlags::ToEnd>} },
Expand Down

0 comments on commit 91171fa

Please sign in to comment.