mirror of
https://github.com/RGBCube/serenity
synced 2025-05-30 22:38:12 +00:00
LibGUI: Move the undo stack from GTextEditor to GTextDocument
Since the same document can be edited by multiple editors, we need to keep the undo stack with the document for coherency.
This commit is contained in:
parent
d6426e4af9
commit
ee8773c586
4 changed files with 319 additions and 284 deletions
|
@ -1,4 +1,5 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/CTimer.h>
|
||||
#include <LibGUI/GTextDocument.h>
|
||||
#include <ctype.h>
|
||||
|
||||
|
@ -7,6 +8,12 @@ GTextDocument::GTextDocument(Client* client)
|
|||
if (client)
|
||||
m_clients.set(client);
|
||||
append_line(make<GTextDocumentLine>(*this));
|
||||
|
||||
// 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, [this] {
|
||||
update_undo_timer();
|
||||
});
|
||||
}
|
||||
|
||||
void GTextDocument::set_text(const StringView& text)
|
||||
|
@ -175,6 +182,11 @@ void GTextDocument::unregister_client(Client& client)
|
|||
}
|
||||
|
||||
void GTextDocument::update_views(Badge<GTextDocumentLine>)
|
||||
{
|
||||
notify_did_change();
|
||||
}
|
||||
|
||||
void GTextDocument::notify_did_change()
|
||||
{
|
||||
if (m_client_notifications_enabled) {
|
||||
for (auto* client : m_clients)
|
||||
|
@ -182,6 +194,14 @@ void GTextDocument::update_views(Badge<GTextDocumentLine>)
|
|||
}
|
||||
}
|
||||
|
||||
void GTextDocument::set_all_cursors(const GTextPosition& position)
|
||||
{
|
||||
if (m_client_notifications_enabled) {
|
||||
for (auto* client : m_clients)
|
||||
client->document_did_set_cursor(position);
|
||||
}
|
||||
}
|
||||
|
||||
String GTextDocument::text_in_range(const GTextRange& a_range) const
|
||||
{
|
||||
auto range = a_range.normalized();
|
||||
|
@ -341,3 +361,196 @@ Optional<GTextDocumentSpan> GTextDocument::first_non_skippable_span_after(const
|
|||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void GTextDocument::undo()
|
||||
{
|
||||
if (!can_undo())
|
||||
return;
|
||||
|
||||
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.is_empty()) {
|
||||
m_undo_stack.remove(m_undo_stack_index);
|
||||
undo();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < undo_vector.size(); i++) {
|
||||
auto& undo_command = undo_vector[i];
|
||||
undo_command.undo();
|
||||
}
|
||||
|
||||
m_undo_stack_index++;
|
||||
notify_did_change();
|
||||
}
|
||||
|
||||
void GTextDocument::redo()
|
||||
{
|
||||
if (!can_redo())
|
||||
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--;
|
||||
notify_did_change();
|
||||
}
|
||||
|
||||
void GTextDocument::add_to_undo_stack(NonnullOwnPtr<GTextDocumentUndoCommand> undo_command)
|
||||
{
|
||||
if (m_undo_stack.is_empty()) {
|
||||
auto undo_commands_container = make<UndoCommandsContainer>();
|
||||
m_undo_stack.prepend(move(undo_commands_container));
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
GTextDocumentUndoCommand::GTextDocumentUndoCommand(GTextDocument& document)
|
||||
: m_document(document)
|
||||
{
|
||||
}
|
||||
|
||||
GTextDocumentUndoCommand::~GTextDocumentUndoCommand()
|
||||
{
|
||||
}
|
||||
|
||||
InsertCharacterCommand::InsertCharacterCommand(GTextDocument& document, char ch, GTextPosition text_position)
|
||||
: GTextDocumentUndoCommand(document)
|
||||
, m_character(ch)
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
RemoveCharacterCommand::RemoveCharacterCommand(GTextDocument& document, char ch, GTextPosition text_position)
|
||||
: GTextDocumentUndoCommand(document)
|
||||
, m_character(ch)
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
RemoveLineCommand::RemoveLineCommand(GTextDocument& document, String line_content, GTextPosition text_position, bool has_merged_content)
|
||||
: GTextDocumentUndoCommand(document)
|
||||
, m_line_content(move(line_content))
|
||||
, m_text_position(text_position)
|
||||
, m_has_merged_content(has_merged_content)
|
||||
{
|
||||
}
|
||||
|
||||
CreateLineCommand::CreateLineCommand(GTextDocument& document, Vector<char> line_content, GTextPosition text_position)
|
||||
: GTextDocumentUndoCommand(document)
|
||||
, m_line_content(move(line_content))
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
void InsertCharacterCommand::undo()
|
||||
{
|
||||
m_document.lines()[m_text_position.line()].remove(m_document, (m_text_position.column() - 1));
|
||||
m_document.notify_did_change();
|
||||
}
|
||||
|
||||
void InsertCharacterCommand::redo()
|
||||
{
|
||||
m_document.lines()[m_text_position.line()].insert(m_document, m_text_position.column() - 1, m_character);
|
||||
}
|
||||
|
||||
void RemoveCharacterCommand::undo()
|
||||
{
|
||||
m_document.lines()[m_text_position.line()].insert(m_document, m_text_position.column(), m_character);
|
||||
}
|
||||
|
||||
void RemoveCharacterCommand::redo()
|
||||
{
|
||||
m_document.lines()[m_text_position.line()].remove(m_document, (m_text_position.column()));
|
||||
m_document.notify_did_change();
|
||||
}
|
||||
|
||||
void RemoveLineCommand::undo()
|
||||
{
|
||||
// Insert back the line
|
||||
m_document.insert_line(m_text_position.line(), make<GTextDocumentLine>(m_document, m_line_content));
|
||||
|
||||
// Remove the merged line contents
|
||||
if (m_has_merged_content) {
|
||||
for (int i = m_line_content.length() - 1; i >= 0; i--)
|
||||
m_document.lines()[m_text_position.line() - 1].remove(m_document, (m_text_position.column()) + i);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveLineCommand::redo()
|
||||
{
|
||||
// Remove the created line
|
||||
m_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_document.lines()[m_text_position.line() - 1].insert(m_document, (m_text_position.column()) + i, m_line_content[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void CreateLineCommand::undo()
|
||||
{
|
||||
// Insert back the created line portion
|
||||
for (int i = 0; i < m_line_content.size(); i++)
|
||||
m_document.lines()[m_text_position.line()].insert(m_document, (m_text_position.column() - 1) + i, m_line_content[i]);
|
||||
|
||||
// Move the cursor up a row back before the split.
|
||||
m_document.set_all_cursors({ m_text_position.line(), m_document.lines()[m_text_position.line()].length() });
|
||||
|
||||
// Remove the created line
|
||||
m_document.remove_line(m_text_position.line() + 1);
|
||||
}
|
||||
|
||||
void CreateLineCommand::redo()
|
||||
{
|
||||
// Remove the characters that we're inserted back
|
||||
for (int i = m_line_content.size() - 1; i >= 0; i--)
|
||||
m_document.lines()[m_text_position.line()].remove(m_document, (m_text_position.column()) + i);
|
||||
|
||||
m_document.notify_did_change();
|
||||
|
||||
// Then we want to add BACK the created line
|
||||
m_document.insert_line(m_text_position.line() + 1, make<GTextDocumentLine>(m_document, ""));
|
||||
|
||||
for (int i = 0; i < m_line_content.size(); i++)
|
||||
m_document.lines()[m_text_position.line() + 1].insert(m_document, i, m_line_content[i]);
|
||||
}
|
||||
|
||||
void GTextDocument::update_undo_timer()
|
||||
{
|
||||
if (m_undo_stack.is_empty())
|
||||
return;
|
||||
|
||||
auto& undo_vector = m_undo_stack[0].m_undo_vector;
|
||||
|
||||
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();
|
||||
|
||||
// Shift the index to the left since we're adding an empty container.
|
||||
if (m_undo_stack_index > 0)
|
||||
m_undo_stack_index++;
|
||||
}
|
||||
|
||||
m_last_updated_undo_vector_size = undo_vector.size();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue