1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 19:45:08 +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,40 +1542,58 @@ 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
// 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)
// Remove the merged line contents
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()
{
//Insert back the created line portion
// Insert back the created line portion
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
// Remove the created line
m_text_editor.document().remove_line(m_text_position.line() + 1);
}
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]);
}