mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 13:48: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();
|
||||
}
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
#include <AK/NonnullOwnPtrVector.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/RefCounted.h>
|
||||
#include <LibCore/CTimer.h>
|
||||
#include <LibDraw/Color.h>
|
||||
#include <LibDraw/Font.h>
|
||||
#include <LibGUI/GTextRange.h>
|
||||
|
||||
class GTextEditor;
|
||||
class GTextDocument;
|
||||
class GTextDocumentLine;
|
||||
|
||||
struct GTextDocumentSpan {
|
||||
|
@ -21,6 +23,62 @@ struct GTextDocumentSpan {
|
|||
void* data { nullptr };
|
||||
};
|
||||
|
||||
class GTextDocumentUndoCommand {
|
||||
public:
|
||||
GTextDocumentUndoCommand(GTextDocument&);
|
||||
virtual ~GTextDocumentUndoCommand();
|
||||
virtual void undo() {}
|
||||
virtual void redo() {}
|
||||
|
||||
protected:
|
||||
GTextDocument& m_document;
|
||||
};
|
||||
|
||||
class InsertCharacterCommand : public GTextDocumentUndoCommand {
|
||||
public:
|
||||
InsertCharacterCommand(GTextDocument&, char, GTextPosition);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
char m_character;
|
||||
GTextPosition m_text_position;
|
||||
};
|
||||
|
||||
class RemoveCharacterCommand : public GTextDocumentUndoCommand {
|
||||
public:
|
||||
RemoveCharacterCommand(GTextDocument&, char, GTextPosition);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
char m_character;
|
||||
GTextPosition m_text_position;
|
||||
};
|
||||
|
||||
class RemoveLineCommand : public GTextDocumentUndoCommand {
|
||||
public:
|
||||
RemoveLineCommand(GTextDocument&, String, GTextPosition, bool has_merged_content);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
String m_line_content;
|
||||
GTextPosition m_text_position;
|
||||
bool m_has_merged_content;
|
||||
};
|
||||
|
||||
class CreateLineCommand : public GTextDocumentUndoCommand {
|
||||
public:
|
||||
CreateLineCommand(GTextDocument&, Vector<char> line_content, GTextPosition);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
Vector<char> m_line_content;
|
||||
GTextPosition m_text_position;
|
||||
};
|
||||
|
||||
class GTextDocument : public RefCounted<GTextDocument> {
|
||||
public:
|
||||
enum class SearchShouldWrap {
|
||||
|
@ -37,6 +95,7 @@ public:
|
|||
virtual void document_did_remove_all_lines() = 0;
|
||||
virtual void document_did_change() = 0;
|
||||
virtual void document_did_set_text() = 0;
|
||||
virtual void document_did_set_cursor(const GTextPosition&) = 0;
|
||||
};
|
||||
|
||||
static NonnullRefPtr<GTextDocument> create(Client* client = nullptr)
|
||||
|
@ -84,14 +143,37 @@ public:
|
|||
Optional<GTextDocumentSpan> first_non_skippable_span_before(const GTextPosition&) const;
|
||||
Optional<GTextDocumentSpan> first_non_skippable_span_after(const GTextPosition&) const;
|
||||
|
||||
struct UndoCommandsContainer {
|
||||
NonnullOwnPtrVector<GTextDocumentUndoCommand> m_undo_vector;
|
||||
};
|
||||
|
||||
void add_to_undo_stack(NonnullOwnPtr<GTextDocumentUndoCommand>);
|
||||
|
||||
bool can_undo() const { return m_undo_stack_index < m_undo_stack.size() && !m_undo_stack.is_empty(); }
|
||||
bool can_redo() const { return m_undo_stack_index > 0 && m_undo_stack[m_undo_stack_index - 1].m_undo_vector.size() > 0 && !m_undo_stack.is_empty(); }
|
||||
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
void notify_did_change();
|
||||
void set_all_cursors(const GTextPosition&);
|
||||
|
||||
private:
|
||||
explicit GTextDocument(Client* client);
|
||||
|
||||
void update_undo_timer();
|
||||
|
||||
NonnullOwnPtrVector<GTextDocumentLine> m_lines;
|
||||
Vector<GTextDocumentSpan> m_spans;
|
||||
|
||||
HashTable<Client*> m_clients;
|
||||
bool m_client_notifications_enabled { true };
|
||||
|
||||
NonnullOwnPtrVector<UndoCommandsContainer> m_undo_stack;
|
||||
int m_undo_stack_index { 0 };
|
||||
int m_last_updated_undo_vector_size = 0;
|
||||
|
||||
RefPtr<CTimer> m_undo_timer;
|
||||
};
|
||||
|
||||
class GTextDocumentLine {
|
||||
|
|
|
@ -35,13 +35,6 @@ 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()
|
||||
|
@ -479,47 +472,6 @@ void GTextEditor::select_all()
|
|||
update();
|
||||
}
|
||||
|
||||
void GTextEditor::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++;
|
||||
did_change();
|
||||
}
|
||||
|
||||
void GTextEditor::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--;
|
||||
did_change();
|
||||
}
|
||||
|
||||
void GTextEditor::get_selection_line_boundaries(int& first_line, int& last_line)
|
||||
{
|
||||
auto selection = normalized_selection();
|
||||
|
@ -817,7 +769,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
for (int i = 0; i < erase_count; ++i) {
|
||||
int row = m_cursor.line();
|
||||
int column = m_cursor.column() - 1 - i;
|
||||
add_to_undo_stack(make<RemoveCharacterCommand>(*this, document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
document().add_to_undo_stack(make<RemoveCharacterCommand>(document(), document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
current_line().remove(document(), m_cursor.column() - 1 - i);
|
||||
}
|
||||
update_content_size();
|
||||
|
@ -832,7 +784,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
|
||||
int row = m_cursor.line();
|
||||
int column = previous_length;
|
||||
add_to_undo_stack(make<RemoveLineCommand>(*this, String(lines()[m_cursor.line()].view()), GTextPosition(row, column), true));
|
||||
document().add_to_undo_stack(make<RemoveLineCommand>(document(), String(lines()[m_cursor.line()].view()), GTextPosition(row, column), true));
|
||||
|
||||
previous_line.append(document(), current_line().characters(), current_line().length());
|
||||
document().remove_line(m_cursor.line());
|
||||
|
@ -938,7 +890,7 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
Vector<char> line_content;
|
||||
for (int i = m_cursor.column(); i < document().lines()[row].length(); i++)
|
||||
line_content.append(document().lines()[row].characters()[i]);
|
||||
add_to_undo_stack(make<CreateLineCommand>(*this, line_content, GTextPosition(row, column)));
|
||||
document().add_to_undo_stack(make<CreateLineCommand>(document(), line_content, GTextPosition(row, column)));
|
||||
|
||||
document().insert_line(m_cursor.line() + (at_tail ? 1 : 0), make<GTextDocumentLine>(document(), new_line_contents));
|
||||
update();
|
||||
|
@ -954,7 +906,7 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
Vector<char> line_content;
|
||||
for (int i = 0; i < new_line->length(); i++)
|
||||
line_content.append(new_line->characters()[i]);
|
||||
add_to_undo_stack(make<CreateLineCommand>(*this, line_content, GTextPosition(row, column)));
|
||||
document().add_to_undo_stack(make<CreateLineCommand>(document(), line_content, GTextPosition(row, column)));
|
||||
|
||||
current_line().truncate(document(), m_cursor.column());
|
||||
document().insert_line(m_cursor.line() + 1, move(new_line));
|
||||
|
@ -977,7 +929,7 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
did_change();
|
||||
set_cursor(m_cursor.line(), m_cursor.column() + 1);
|
||||
|
||||
add_to_undo_stack(make<InsertCharacterCommand>(*this, ch, m_cursor));
|
||||
document().add_to_undo_stack(make<InsertCharacterCommand>(document(), ch, m_cursor));
|
||||
}
|
||||
|
||||
int GTextEditor::content_x_for_position(const GTextPosition& position) const
|
||||
|
@ -1090,27 +1042,6 @@ void GTextEditor::update_cursor()
|
|||
update(line_widget_rect(m_cursor.line()));
|
||||
}
|
||||
|
||||
void GTextEditor::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();
|
||||
}
|
||||
|
||||
void GTextEditor::set_cursor(int line, int column)
|
||||
{
|
||||
set_cursor({ line, column });
|
||||
|
@ -1249,7 +1180,7 @@ void GTextEditor::delete_selection()
|
|||
for (int i = selection.start().line() + 1; i < selection.end().line();) {
|
||||
int row = i;
|
||||
int column = lines()[i].length();
|
||||
add_to_undo_stack(make<RemoveLineCommand>(*this, String(lines()[i].view()), GTextPosition(row, column), false));
|
||||
document().add_to_undo_stack(make<RemoveLineCommand>(document(), String(lines()[i].view()), GTextPosition(row, column), false));
|
||||
|
||||
document().remove_line(i);
|
||||
selection.end().set_line(selection.end().line() - 1);
|
||||
|
@ -1263,7 +1194,7 @@ void GTextEditor::delete_selection()
|
|||
for (int i = selection.end().column() - 1; i >= selection.start().column(); i--) {
|
||||
int row = selection.start().line();
|
||||
int column = i;
|
||||
add_to_undo_stack(make<RemoveCharacterCommand>(*this, document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
document().add_to_undo_stack(make<RemoveCharacterCommand>(document(), document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
}
|
||||
|
||||
if (whole_line_is_selected) {
|
||||
|
@ -1290,16 +1221,16 @@ void GTextEditor::delete_selection()
|
|||
for (int i = first_line.length() - 1; i > selection.start().column() - 1; i--) {
|
||||
int row = selection.start().line();
|
||||
int column = i;
|
||||
add_to_undo_stack(make<RemoveCharacterCommand>(*this, document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
document().add_to_undo_stack(make<RemoveCharacterCommand>(document(), document().line(row).characters()[column], GTextPosition(row, column)));
|
||||
}
|
||||
|
||||
add_to_undo_stack(make<RemoveLineCommand>(*this, String(second_line.view()), selection.end(), false));
|
||||
document().add_to_undo_stack(make<RemoveLineCommand>(document(), String(second_line.view()), selection.end(), false));
|
||||
|
||||
first_line.set_text(document(), builder.to_string());
|
||||
document().remove_line(selection.end().line());
|
||||
|
||||
for (int i = (first_line.length()) - after_selection.length(); i < first_line.length(); i++)
|
||||
add_to_undo_stack(make<InsertCharacterCommand>(*this, first_line.characters()[i], GTextPosition(selection.start().line(), i + 1)));
|
||||
document().add_to_undo_stack(make<InsertCharacterCommand>(document(), first_line.characters()[i], GTextPosition(selection.start().line(), i + 1)));
|
||||
}
|
||||
|
||||
if (lines().is_empty()) {
|
||||
|
@ -1464,25 +1395,6 @@ void GTextEditor::ensure_cursor_is_valid()
|
|||
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.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));
|
||||
}
|
||||
|
||||
int GTextEditor::visual_line_containing(int line_index, int column) const
|
||||
{
|
||||
int visual_line_index = 0;
|
||||
|
@ -1609,8 +1521,12 @@ void GTextEditor::document_did_insert_line(int line_index)
|
|||
|
||||
void GTextEditor::document_did_change()
|
||||
{
|
||||
ensure_cursor_is_valid();
|
||||
recompute_all_visual_lines();
|
||||
update();
|
||||
|
||||
undo_action().set_enabled(can_undo());
|
||||
redo_action().set_enabled(can_redo());
|
||||
}
|
||||
|
||||
void GTextEditor::document_did_set_text()
|
||||
|
@ -1621,6 +1537,11 @@ void GTextEditor::document_did_set_text()
|
|||
document_did_change();
|
||||
}
|
||||
|
||||
void GTextEditor::document_did_set_cursor(const GTextPosition& position)
|
||||
{
|
||||
set_cursor(position);
|
||||
}
|
||||
|
||||
void GTextEditor::set_document(GTextDocument& document)
|
||||
{
|
||||
if (m_document.ptr() == &document)
|
||||
|
@ -1638,121 +1559,6 @@ void GTextEditor::set_document(GTextDocument& document)
|
|||
m_document->register_client(*this);
|
||||
}
|
||||
|
||||
GTextEditor::UndoCommand::UndoCommand(GTextEditor& text_editor)
|
||||
: m_text_editor(text_editor)
|
||||
{
|
||||
}
|
||||
|
||||
GTextEditor::UndoCommand::~UndoCommand()
|
||||
{
|
||||
}
|
||||
|
||||
void GTextEditor::UndoCommand::undo() {}
|
||||
void GTextEditor::UndoCommand::redo() {}
|
||||
|
||||
GTextEditor::InsertCharacterCommand::InsertCharacterCommand(GTextEditor& text_editor, char ch, GTextPosition text_position)
|
||||
: UndoCommand(text_editor)
|
||||
, m_character(ch)
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
GTextEditor::RemoveCharacterCommand::RemoveCharacterCommand(GTextEditor& text_editor, char ch, GTextPosition text_position)
|
||||
: UndoCommand(text_editor)
|
||||
, m_character(ch)
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
GTextEditor::RemoveLineCommand::RemoveLineCommand(GTextEditor& text_editor, String line_content, GTextPosition text_position, bool has_merged_content)
|
||||
: UndoCommand(text_editor)
|
||||
, m_line_content(line_content)
|
||||
, m_text_position(text_position)
|
||||
, m_has_merged_content(has_merged_content)
|
||||
{
|
||||
}
|
||||
|
||||
GTextEditor::CreateLineCommand::CreateLineCommand(GTextEditor& text_editor, Vector<char> line_content, GTextPosition text_position)
|
||||
: UndoCommand(text_editor)
|
||||
, m_line_content(line_content)
|
||||
, m_text_position(text_position)
|
||||
{
|
||||
}
|
||||
|
||||
void GTextEditor::InsertCharacterCommand::undo()
|
||||
{
|
||||
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()
|
||||
{
|
||||
m_text_editor.lines()[m_text_position.line()].insert(m_text_editor.document(), m_text_position.column() - 1, m_character);
|
||||
}
|
||||
|
||||
void GTextEditor::RemoveCharacterCommand::undo()
|
||||
{
|
||||
m_text_editor.lines()[m_text_position.line()].insert(m_text_editor.document(), m_text_position.column(), m_character);
|
||||
}
|
||||
|
||||
void GTextEditor::RemoveCharacterCommand::redo()
|
||||
{
|
||||
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) {
|
||||
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()
|
||||
{
|
||||
// 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
|
||||
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]);
|
||||
|
||||
// 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
|
||||
m_text_editor.document().remove_line(m_text_position.line() + 1);
|
||||
}
|
||||
|
||||
void GTextEditor::CreateLineCommand::redo()
|
||||
{
|
||||
// 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]);
|
||||
}
|
||||
|
||||
void GTextEditor::flush_pending_change_notification_if_needed()
|
||||
{
|
||||
if (!m_has_pending_change_notification)
|
||||
|
|
|
@ -69,8 +69,8 @@ public:
|
|||
bool has_selection() const { return m_selection.is_valid(); }
|
||||
String selected_text() const;
|
||||
void set_selection(const GTextRange&);
|
||||
bool can_undo() const { return m_undo_stack_index < m_undo_stack.size() && !m_undo_stack.is_empty(); }
|
||||
bool can_redo() const { return m_undo_stack_index > 0 && m_undo_stack[m_undo_stack_index - 1].m_undo_vector.size() > 0 && !m_undo_stack.is_empty(); }
|
||||
bool can_undo() const { return document().can_undo(); }
|
||||
bool can_redo() const { return document().can_redo(); }
|
||||
|
||||
String text() const;
|
||||
|
||||
|
@ -82,8 +82,8 @@ public:
|
|||
void do_delete();
|
||||
void delete_current_line();
|
||||
void select_all();
|
||||
void undo();
|
||||
void redo();
|
||||
void undo() { document().undo(); }
|
||||
void redo() { document().redo(); }
|
||||
|
||||
Function<void()> on_change;
|
||||
Function<void()> on_return_pressed;
|
||||
|
@ -134,6 +134,7 @@ private:
|
|||
virtual void document_did_remove_all_lines() override;
|
||||
virtual void document_did_change() override;
|
||||
virtual void document_did_set_text() override;
|
||||
virtual void document_did_set_cursor(const GTextPosition&) override;
|
||||
|
||||
void create_actions();
|
||||
void paint_ruler(Painter&);
|
||||
|
@ -145,7 +146,6 @@ private:
|
|||
Rect cursor_content_rect() const;
|
||||
Rect content_rect_for_position(const GTextPosition&) const;
|
||||
void update_cursor();
|
||||
void update_undo_timer();
|
||||
const NonnullOwnPtrVector<GTextDocumentLine>& lines() const { return document().lines(); }
|
||||
NonnullOwnPtrVector<GTextDocumentLine>& lines() { return document().lines(); }
|
||||
GTextDocumentLine& line(int index) { return document().line(index); }
|
||||
|
@ -171,68 +171,6 @@ private:
|
|||
void move_selected_lines_down();
|
||||
void sort_selected_lines();
|
||||
|
||||
class UndoCommand {
|
||||
|
||||
public:
|
||||
UndoCommand(GTextEditor& text_editor);
|
||||
virtual ~UndoCommand();
|
||||
virtual void undo();
|
||||
virtual void redo();
|
||||
|
||||
protected:
|
||||
GTextEditor& m_text_editor;
|
||||
};
|
||||
|
||||
class InsertCharacterCommand : public UndoCommand {
|
||||
public:
|
||||
InsertCharacterCommand(GTextEditor& text_editor, char ch, GTextPosition text_position);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
char m_character;
|
||||
GTextPosition m_text_position;
|
||||
};
|
||||
|
||||
class RemoveCharacterCommand : public UndoCommand {
|
||||
public:
|
||||
RemoveCharacterCommand(GTextEditor& text_editor, char ch, GTextPosition text_position);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
char m_character;
|
||||
GTextPosition m_text_position;
|
||||
};
|
||||
|
||||
class RemoveLineCommand : public UndoCommand {
|
||||
public:
|
||||
RemoveLineCommand(GTextEditor& text_editor, String, GTextPosition text_position, bool has_merged_content);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
String m_line_content;
|
||||
GTextPosition m_text_position;
|
||||
bool m_has_merged_content;
|
||||
};
|
||||
|
||||
class CreateLineCommand : public UndoCommand {
|
||||
public:
|
||||
CreateLineCommand(GTextEditor& text_editor, Vector<char> line_content, GTextPosition text_position);
|
||||
virtual void undo() override;
|
||||
virtual void redo() override;
|
||||
|
||||
private:
|
||||
Vector<char> m_line_content;
|
||||
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);
|
||||
|
||||
|
@ -260,10 +198,6 @@ private:
|
|||
RefPtr<GAction> m_delete_action;
|
||||
CElapsedTimer m_triple_click_timer;
|
||||
NonnullRefPtrVector<GAction> m_custom_context_menu_actions;
|
||||
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;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue