1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 13:38:11 +00:00

TextEditor: Added redo functionality & proper stack manipulation

Added redo functionality & added m_undo_stack_index that moves back &
forth on the stack depending on the undo's & redo's.
This commit is contained in:
rhin123 2019-11-05 16:37:47 -06:00 committed by Andreas Kling
parent 306cd0d840
commit 96c1e36ed3
2 changed files with 97 additions and 44 deletions

View file

@ -29,6 +29,13 @@ GTextEditor::GTextEditor(Type type, GWidget* parent)
vertical_scrollbar().set_step(line_height()); vertical_scrollbar().set_step(line_height());
m_cursor = { 0, 0 }; m_cursor = { 0, 0 };
create_actions(); create_actions();
// TODO: Instead of a repating timer, this we should call a delayed 2 sec timer when the user types.
m_undo_timer = CTimer::construct(
2000, [&] {
update_undo_timer();
},
this);
} }
GTextEditor::~GTextEditor() GTextEditor::~GTextEditor()
@ -456,14 +463,15 @@ void GTextEditor::select_all()
void GTextEditor::undo() void GTextEditor::undo()
{ {
if (m_undo_stack.size() <= 0) if (m_undo_stack_index >= m_undo_stack.size() || m_undo_stack.is_empty())
return; return;
auto& undo_vector = m_undo_stack[m_undo_index]; auto& undo_container = m_undo_stack[m_undo_stack_index];
auto& undo_vector = undo_container.m_undo_vector;
//If we try to undo a empty vector, delete it and skip over. //If we try to undo a empty vector, delete it and skip over.
if (undo_vector.size() <= 0 && m_undo_index > 0) { if (undo_vector.is_empty()) {
m_undo_index--; m_undo_stack.remove(m_undo_stack_index);
undo(); undo();
return; return;
} }
@ -473,14 +481,23 @@ void GTextEditor::undo()
undo_command.undo(); undo_command.undo();
} }
undo_vector.clear(); m_undo_stack_index++;
m_undo_stack.remove(m_undo_index);
if (m_undo_index > 0)
m_undo_index--;
} }
void GTextEditor::redo() void GTextEditor::redo()
{ {
if (m_undo_stack_index <= 0 || m_undo_stack.is_empty())
return;
auto& undo_container = m_undo_stack[m_undo_stack_index - 1];
auto& redo_vector = undo_container.m_undo_vector;
for (int i = redo_vector.size() - 1; i >= 0; i--) {
auto& undo_command = redo_vector[i];
undo_command.redo();
}
m_undo_stack_index--;
} }
void GTextEditor::keydown_event(GKeyEvent& event) void GTextEditor::keydown_event(GKeyEvent& event)
@ -933,23 +950,23 @@ void GTextEditor::update_cursor()
void GTextEditor::update_undo_timer() void GTextEditor::update_undo_timer()
{ {
if (m_undo_stack.size() <= 0) if (m_undo_stack.is_empty())
return; return;
if (m_undo_timer == 0) auto& undo_vector = m_undo_stack[0].m_undo_vector;
m_prev_undo_stack_size = m_undo_stack[m_undo_index].size();
if (m_undo_timer >= 2 && m_undo_stack[m_undo_index].size() > 0) { if (undo_vector.size() == m_last_updated_undo_vector_size && !undo_vector.is_empty()) {
auto undo_commands_container = make<UndoCommandsContainer>();
m_undo_stack.prepend(move(undo_commands_container));
// Note: Remove dbg() if we're 100% sure there are no bugs left.
dbg() << "Undo stack increased to " << m_undo_stack.size();
if (m_undo_stack[m_undo_index].size() == m_prev_undo_stack_size) { // Shift the index to the left since we're adding an empty container.
dbg() << "Increased Undo Index"; if (m_undo_stack_index > 0)
m_undo_stack.append(make<NonnullOwnPtrVector<UndoCommand>>()); m_undo_stack_index++;
m_undo_index++;
}
m_undo_timer = -1;
} }
m_undo_timer++; m_last_updated_undo_vector_size = undo_vector.size();
} }
void GTextEditor::set_cursor(int line, int column) void GTextEditor::set_cursor(int line, int column)
@ -998,10 +1015,8 @@ void GTextEditor::focusout_event(CEvent&)
void GTextEditor::timer_event(CTimerEvent&) void GTextEditor::timer_event(CTimerEvent&)
{ {
m_cursor_state = !m_cursor_state; m_cursor_state = !m_cursor_state;
if (is_focused()) { if (is_focused())
update_cursor(); update_cursor();
update_undo_timer();
}
} }
bool GTextEditor::write_to_file(const StringView& path) bool GTextEditor::write_to_file(const StringView& path)
@ -1296,12 +1311,29 @@ void GTextEditor::recompute_all_visual_lines()
update_content_size(); update_content_size();
} }
void GTextEditor::ensure_cursor_is_valid()
{
if (cursor().column() > lines()[cursor().line()].length())
set_cursor(cursor().line(), cursor().column() - (lines()[cursor().line()].length() - cursor().column()));
}
void GTextEditor::add_to_undo_stack(NonnullOwnPtr<UndoCommand> undo_command) void GTextEditor::add_to_undo_stack(NonnullOwnPtr<UndoCommand> undo_command)
{ {
if (m_undo_stack.size() <= m_undo_index) if (m_undo_stack.is_empty()) {
m_undo_stack.append(make<NonnullOwnPtrVector<UndoCommand>>()); auto undo_commands_container = make<UndoCommandsContainer>();
m_undo_stack.prepend(move(undo_commands_container));
}
m_undo_stack[(m_undo_index)].insert(0, move(undo_command)); // Clear the elements of the stack before the m_undo_stack_index (Excluding our new element)
for (int i = 1; i < m_undo_stack_index; i++)
m_undo_stack.remove(1);
if (m_undo_stack_index > 0 && !m_undo_stack.is_empty())
m_undo_stack[0].m_undo_vector.clear();
m_undo_stack_index = 0;
m_undo_stack[0].m_undo_vector.prepend(move(undo_command));
} }
int GTextEditor::visual_line_containing(int line_index, int column) const int GTextEditor::visual_line_containing(int line_index, int column) const
@ -1494,16 +1526,13 @@ GTextEditor::CreateLineCommand::CreateLineCommand(GTextEditor& text_editor, Vect
void GTextEditor::InsertCharacterCommand::undo() void GTextEditor::InsertCharacterCommand::undo()
{ {
//Move back the cursor if it's inside in deleted content
if (m_text_editor.cursor().column() >= m_text_position.column())
m_text_editor.set_cursor(m_text_position.line(), m_text_position.column() - 1);
m_text_editor.lines()[m_text_position.line()].remove(m_text_editor.document(), (m_text_position.column() - 1)); m_text_editor.lines()[m_text_position.line()].remove(m_text_editor.document(), (m_text_position.column() - 1));
m_text_editor.ensure_cursor_is_valid();
} }
void GTextEditor::InsertCharacterCommand::redo() void GTextEditor::InsertCharacterCommand::redo()
{ {
//TOOD: Redo implementation m_text_editor.lines()[m_text_position.line()].insert(m_text_editor.document(), m_text_position.column() - 1, m_character);
} }
void GTextEditor::RemoveCharacterCommand::undo() void GTextEditor::RemoveCharacterCommand::undo()
@ -1513,40 +1542,58 @@ void GTextEditor::RemoveCharacterCommand::undo()
void GTextEditor::RemoveCharacterCommand::redo() void GTextEditor::RemoveCharacterCommand::redo()
{ {
//TOOD: Redo implementation m_text_editor.lines()[m_text_position.line()].remove(m_text_editor.document(), (m_text_position.column()));
m_text_editor.ensure_cursor_is_valid();
} }
void GTextEditor::RemoveLineCommand::undo() void GTextEditor::RemoveLineCommand::undo()
{ {
// Insert back the line
//Insert back the line
m_text_editor.document().insert_line(m_text_position.line(), make<GTextDocumentLine>(m_text_editor.document(), m_line_content)); m_text_editor.document().insert_line(m_text_position.line(), make<GTextDocumentLine>(m_text_editor.document(), m_line_content));
//Remove the merged line contents // Remove the merged line contents
if (m_has_merged_content) if (m_has_merged_content) {
for (int i = m_line_content.length() - 1; i >= 0; i--) for (int i = m_line_content.length() - 1; i >= 0; i--)
m_text_editor.document().lines()[m_text_position.line() - 1].remove(m_text_editor.document(), (m_text_position.column()) + i); m_text_editor.document().lines()[m_text_position.line() - 1].remove(m_text_editor.document(), (m_text_position.column()) + i);
}
} }
void GTextEditor::RemoveLineCommand::redo() void GTextEditor::RemoveLineCommand::redo()
{ {
//TOOD: Redo implementation // Remove the created line
m_text_editor.document().remove_line(m_text_position.line());
// Add back the line contents
if (m_has_merged_content) {
for (int i = 0; i < m_line_content.length(); i++)
m_text_editor.document().lines()[m_text_position.line() - 1].insert(m_text_editor.document(), (m_text_position.column()) + i, m_line_content[i]);
}
} }
void GTextEditor::CreateLineCommand::undo() void GTextEditor::CreateLineCommand::undo()
{ {
//Insert back the created line portion // Insert back the created line portion
for (int i = 0; i < m_line_content.size(); i++) for (int i = 0; i < m_line_content.size(); i++)
m_text_editor.document().lines()[m_text_position.line()].insert(m_text_editor.document(), (m_text_position.column() - 1) + i, m_line_content[i]); m_text_editor.document().lines()[m_text_position.line()].insert(m_text_editor.document(), (m_text_position.column() - 1) + i, m_line_content[i]);
//Set the cursor back before the selection // Move the cursor up a row back before the split.
m_text_editor.set_cursor(m_text_position.line(), m_text_editor.document().lines()[m_text_position.line()].length()); m_text_editor.set_cursor(m_text_position.line(), m_text_editor.document().lines()[m_text_position.line()].length());
//Remove the created line // Remove the created line
m_text_editor.document().remove_line(m_text_position.line() + 1); m_text_editor.document().remove_line(m_text_position.line() + 1);
} }
void GTextEditor::CreateLineCommand::redo() void GTextEditor::CreateLineCommand::redo()
{ {
//TOOD: Redo implementation // Remove the characters that we're inserted back
for (int i = m_line_content.size() - 1; i >= 0; i--)
m_text_editor.document().lines()[m_text_position.line()].remove(m_text_editor.document(), (m_text_position.column()) + i);
m_text_editor.ensure_cursor_is_valid();
// Then we want to add BACK the created line
m_text_editor.document().insert_line(m_text_position.line() + 1, make<GTextDocumentLine>(m_text_editor.document(), ""));
for (int i = 0; i < m_line_content.size(); i++)
m_text_editor.document().lines()[m_text_position.line() + 1].insert(m_text_editor.document(), i, m_line_content[i]);
} }

View file

@ -4,6 +4,7 @@
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <AK/NonnullRefPtrVector.h> #include <AK/NonnullRefPtrVector.h>
#include <LibCore/CTimer.h>
#include <LibDraw/TextAlignment.h> #include <LibDraw/TextAlignment.h>
#include <LibGUI/GScrollableWidget.h> #include <LibGUI/GScrollableWidget.h>
#include <LibGUI/GTextDocument.h> #include <LibGUI/GTextDocument.h>
@ -158,6 +159,7 @@ private:
Rect ruler_rect_in_inner_coordinates() const; Rect ruler_rect_in_inner_coordinates() const;
Rect visible_text_rect_in_inner_coordinates() const; Rect visible_text_rect_in_inner_coordinates() const;
void recompute_all_visual_lines(); void recompute_all_visual_lines();
void ensure_cursor_is_valid();
class UndoCommand { class UndoCommand {
@ -216,6 +218,10 @@ private:
GTextPosition m_text_position; GTextPosition m_text_position;
}; };
struct UndoCommandsContainer {
NonnullOwnPtrVector<UndoCommand> m_undo_vector;
};
void add_to_undo_stack(NonnullOwnPtr<UndoCommand> undo_command); void add_to_undo_stack(NonnullOwnPtr<UndoCommand> undo_command);
int visual_line_containing(int line_index, int column) const; int visual_line_containing(int line_index, int column) const;
void recompute_visual_lines(int line_index); void recompute_visual_lines(int line_index);
@ -244,10 +250,10 @@ private:
RefPtr<GAction> m_delete_action; RefPtr<GAction> m_delete_action;
CElapsedTimer m_triple_click_timer; CElapsedTimer m_triple_click_timer;
NonnullRefPtrVector<GAction> m_custom_context_menu_actions; NonnullRefPtrVector<GAction> m_custom_context_menu_actions;
NonnullOwnPtrVector<NonnullOwnPtrVector<UndoCommand>> m_undo_stack; NonnullOwnPtrVector<UndoCommandsContainer> m_undo_stack;
int m_undo_index = 0; int m_undo_stack_index = 0;
int m_undo_timer = 0; RefPtr<CTimer> m_undo_timer;
int m_prev_undo_stack_size = 0; int m_last_updated_undo_vector_size = 0;
RefPtr<GTextDocument> m_document; RefPtr<GTextDocument> m_document;