LibGUI: Remove UndoStack's automatic command combo'ing

UndoStack will now merge adjacent commands *if they want to be merged*
instead of bundling everything you push onto it until you tell it
to "finalize the combo."

This uses less memory and gives applications full control over how
their undo stacks end up. :^)
This commit is contained in:
Andreas Kling 2021-05-08 21:08:41 +02:00
parent 81bc861085
commit 161568103e
5 changed files with 37 additions and 77 deletions

View file

@ -340,10 +340,9 @@ FontEditorWidget::FontEditorWidget(const String& path, RefPtr<Gfx::BitmapFont>&&
did_modify_font();
};
m_glyph_editor_widget->on_undo_event = [this](bool finalize) {
m_glyph_editor_widget->on_undo_event = [this](bool) {
// FIXME: UndoStack no longer has finalization concept, so this needs some fixing.
m_undo_stack->push(make<GlyphUndoCommand>(*m_undo_glyph));
if (finalize)
m_undo_stack->finalize_current_combo();
did_change_undo_stack();
};

View file

@ -48,7 +48,6 @@ void ImageEditor::did_complete_action()
{
if (!m_image)
return;
m_undo_stack->finalize_current_combo();
m_undo_stack->push(make<ImageUndoCommand>(*m_image));
}

View file

@ -726,6 +726,8 @@ bool InsertTextCommand::merge_with(GUI::Command const& other)
auto& typed_other = static_cast<InsertTextCommand const&>(other);
if (m_range.end() != typed_other.m_range.start())
return false;
if (m_range.start().line() != m_range.end().line())
return false;
StringBuilder builder(m_text.length() + typed_other.m_text.length());
builder.append(m_text);
builder.append(typed_other.m_text);
@ -807,16 +809,18 @@ bool RemoveTextCommand::merge_with(GUI::Command const& other)
if (!is<RemoveTextCommand>(other))
return false;
auto& typed_other = static_cast<RemoveTextCommand const&>(other);
if (m_range.start() == typed_other.m_range.end()) {
// Merge backspaces
StringBuilder builder(m_text.length() + typed_other.m_text.length());
builder.append(typed_other.m_text);
builder.append(m_text);
m_text = builder.to_string();
m_range.set_start(typed_other.m_range.start());
return true;
}
// FIXME: Merge forward-deletes
if (m_range.start() != typed_other.m_range.end())
return false;
if (m_range.start().line() != m_range.end().line())
return false;
// Merge backspaces
StringBuilder builder(m_text.length() + typed_other.m_text.length());
builder.append(typed_other.m_text);
builder.append(m_text);
m_text = builder.to_string();
m_range.set_start(typed_other.m_range.start());
return true;
return false;
}
@ -834,7 +838,7 @@ void RemoveTextCommand::undo()
void TextDocument::update_undo()
{
m_undo_stack.finalize_current_combo();
// FIXME: Maybe seal the last command somehow?
}
TextPosition TextDocument::insert_at(const TextPosition& position, const StringView& text, const Client* client)

View file

@ -19,14 +19,14 @@ UndoStack::~UndoStack()
bool UndoStack::can_undo() const
{
return m_stack_index > 0 || (m_stack.size() == 1 && m_stack[0].commands.size() > 0);
return m_stack_index > 0;
}
bool UndoStack::can_redo() const
{
if (m_stack.is_empty())
return false;
return m_stack_index != m_stack.size() - 1;
return m_stack_index != m_stack.size();
}
void UndoStack::undo()
@ -34,10 +34,8 @@ void UndoStack::undo()
if (!can_undo())
return;
finalize_current_combo();
auto& combo = m_stack[--m_stack_index];
for (auto i = static_cast<ssize_t>(combo.commands.size()) - 1; i >= 0; i--)
combo.commands[i].undo();
auto& command = m_stack[--m_stack_index];
command.undo();
if (on_state_change)
on_state_change();
@ -48,69 +46,39 @@ void UndoStack::redo()
if (!can_redo())
return;
auto& commands = m_stack[m_stack_index++].commands;
for (auto& command : commands)
command.redo();
auto& command = m_stack[m_stack_index++];
command.redo();
if (on_state_change)
on_state_change();
}
void UndoStack::pop()
void UndoStack::push(NonnullOwnPtr<Command> command)
{
VERIFY(!m_stack.is_empty());
m_stack.take_last();
// If the stack cursor is behind the top of the stack, nuke everything from here to the top.
while (m_stack.size() != m_stack_index)
m_stack.take_last();
if (m_clean_index.has_value() && m_clean_index.value() > m_stack.size())
m_clean_index = {};
}
void UndoStack::push(NonnullOwnPtr<Command>&& command)
{
if (m_stack.is_empty())
finalize_current_combo();
// If the stack cursor is behind the top of the stack, nuke everything from here to the top.
if (m_stack_index != m_stack.size() - 1) {
while (m_stack.size() != m_stack_index) {
pop();
}
finalize_current_combo();
}
if (!m_stack.last().commands.is_empty()) {
bool merged = m_stack.last().commands.last().merge_with(*command);
if (merged)
if (!m_stack.is_empty()) {
if (m_stack.last().merge_with(*command))
return;
}
m_stack.last().commands.append(move(command));
m_stack.append(move(command));
m_stack_index = m_stack.size();
if (on_state_change)
on_state_change();
}
void UndoStack::finalize_current_combo()
{
if (m_stack.is_empty()) {
m_stack.append(make<Combo>());
return;
}
if (!m_stack.last().commands.is_empty()) {
m_stack.append(make<Combo>());
m_stack_index = m_stack.size() - 1;
if (on_state_change)
on_state_change();
}
}
void UndoStack::set_current_unmodified()
{
finalize_current_combo();
if (m_clean_index.has_value() && m_clean_index.value() == m_stack_index)
return;
m_clean_index = m_stack_index;
if (on_state_change)
@ -119,14 +87,12 @@ void UndoStack::set_current_unmodified()
bool UndoStack::is_current_modified() const
{
if (m_stack.is_empty())
return false;
if (!m_clean_index.has_value())
return true;
if (m_stack_index != m_clean_index.value())
return true;
if (m_stack.is_empty())
return false;
if (m_stack_index == m_stack.size() - 1 && !m_stack[m_stack_index].commands.is_empty())
return true;
return false;
}

View file

@ -17,7 +17,7 @@ public:
UndoStack();
~UndoStack();
void push(NonnullOwnPtr<Command>&&);
void push(NonnullOwnPtr<Command>);
bool can_undo() const;
bool can_redo() const;
@ -25,8 +25,6 @@ public:
void undo();
void redo();
void finalize_current_combo();
void set_current_unmodified();
bool is_current_modified() const;
@ -35,13 +33,7 @@ public:
Function<void()> on_state_change;
private:
struct Combo {
NonnullOwnPtrVector<Command> commands;
};
void pop();
NonnullOwnPtrVector<Combo> m_stack;
NonnullOwnPtrVector<Command> m_stack;
size_t m_stack_index { 0 };
Optional<size_t> m_clean_index;
};