1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14: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());
m_cursor = { 0, 0 };
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()
@ -456,14 +463,15 @@ void GTextEditor::select_all()
void GTextEditor::undo()
{
if (m_undo_stack.size() <= 0)
if (m_undo_stack_index >= m_undo_stack.size() || m_undo_stack.is_empty())
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 (undo_vector.size() <= 0 && m_undo_index > 0) {
m_undo_index--;
if (undo_vector.is_empty()) {
m_undo_stack.remove(m_undo_stack_index);
undo();
return;
}
@ -473,14 +481,23 @@ void GTextEditor::undo()
undo_command.undo();
}
undo_vector.clear();
m_undo_stack.remove(m_undo_index);
if (m_undo_index > 0)
m_undo_index--;
m_undo_stack_index++;
}
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)
@ -933,23 +950,23 @@ void GTextEditor::update_cursor()
void GTextEditor::update_undo_timer()
{
if (m_undo_stack.size() <= 0)
if (m_undo_stack.is_empty())
return;
if (m_undo_timer == 0)
m_prev_undo_stack_size = m_undo_stack[m_undo_index].size();
auto& undo_vector = m_undo_stack[0].m_undo_vector;
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) {
dbg() << "Increased Undo Index";
m_undo_stack.append(make<NonnullOwnPtrVector<UndoCommand>>());
m_undo_index++;
}
m_undo_timer = -1;
// Shift the index to the left since we're adding an empty container.
if (m_undo_stack_index > 0)
m_undo_stack_index++;
}
m_undo_timer++;
m_last_updated_undo_vector_size = undo_vector.size();
}
void GTextEditor::set_cursor(int line, int column)
@ -998,10 +1015,8 @@ void GTextEditor::focusout_event(CEvent&)
void GTextEditor::timer_event(CTimerEvent&)
{
m_cursor_state = !m_cursor_state;
if (is_focused()) {
if (is_focused())
update_cursor();
update_undo_timer();
}
}
bool GTextEditor::write_to_file(const StringView& path)
@ -1296,12 +1311,29 @@ void GTextEditor::recompute_all_visual_lines()
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)
{
if (m_undo_stack.size() <= m_undo_index)
m_undo_stack.append(make<NonnullOwnPtrVector<UndoCommand>>());
if (m_undo_stack.is_empty()) {
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
@ -1494,16 +1526,13 @@ GTextEditor::CreateLineCommand::CreateLineCommand(GTextEditor& text_editor, Vect
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.ensure_cursor_is_valid();
}
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()
@ -1513,24 +1542,32 @@ void GTextEditor::RemoveCharacterCommand::undo()
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()
{
// Insert back the line
m_text_editor.document().insert_line(m_text_position.line(), make<GTextDocumentLine>(m_text_editor.document(), m_line_content));
// 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--)
m_text_editor.document().lines()[m_text_position.line() - 1].remove(m_text_editor.document(), (m_text_position.column()) + i);
}
}
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()
@ -1539,7 +1576,7 @@ void GTextEditor::CreateLineCommand::undo()
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]);
//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());
// Remove the created line
@ -1548,5 +1585,15 @@ void GTextEditor::CreateLineCommand::undo()
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/NonnullOwnPtrVector.h>
#include <AK/NonnullRefPtrVector.h>
#include <LibCore/CTimer.h>
#include <LibDraw/TextAlignment.h>
#include <LibGUI/GScrollableWidget.h>
#include <LibGUI/GTextDocument.h>
@ -158,6 +159,7 @@ private:
Rect ruler_rect_in_inner_coordinates() const;
Rect visible_text_rect_in_inner_coordinates() const;
void recompute_all_visual_lines();
void ensure_cursor_is_valid();
class UndoCommand {
@ -216,6 +218,10 @@ private:
GTextPosition m_text_position;
};
struct UndoCommandsContainer {
NonnullOwnPtrVector<UndoCommand> m_undo_vector;
};
void add_to_undo_stack(NonnullOwnPtr<UndoCommand> undo_command);
int visual_line_containing(int line_index, int column) const;
void recompute_visual_lines(int line_index);
@ -244,10 +250,10 @@ private:
RefPtr<GAction> m_delete_action;
CElapsedTimer m_triple_click_timer;
NonnullRefPtrVector<GAction> m_custom_context_menu_actions;
NonnullOwnPtrVector<NonnullOwnPtrVector<UndoCommand>> m_undo_stack;
int m_undo_index = 0;
int m_undo_timer = 0;
int m_prev_undo_stack_size = 0;
NonnullOwnPtrVector<UndoCommandsContainer> m_undo_stack;
int m_undo_stack_index = 0;
RefPtr<CTimer> m_undo_timer;
int m_last_updated_undo_vector_size = 0;
RefPtr<GTextDocument> m_document;