1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-22 19:15:08 +00:00

LibGUI: Give GTextDocument access to the GTextEditor during commands

It's useful for the GTextDocument to have access to the initiating
GTextEditor widget during the initial execution of a command.

Since commands are executed via calls to GUndoCommand::redo(), we do
this by wrapping the invocation of redo() in a new helper called
GTextDocumentUndoCommand::execute_from(GTextDocument::Client).

This is then used to fetch the current auto-indentation feature state
and the soft tab width, both used by text insertion.
This commit is contained in:
Andreas Kling 2020-01-23 21:04:59 +01:00
parent 03d73cbaae
commit 14b83ee12f
3 changed files with 57 additions and 43 deletions

View file

@ -27,6 +27,7 @@
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibCore/CTimer.h> #include <LibCore/CTimer.h>
#include <LibGUI/GTextDocument.h> #include <LibGUI/GTextDocument.h>
#include <LibGUI/GTextEditor.h>
#include <ctype.h> #include <ctype.h>
GTextDocument::GTextDocument(Client* client) GTextDocument::GTextDocument(Client* client)
@ -427,7 +428,7 @@ InsertTextCommand::InsertTextCommand(GTextDocument& document, const String& text
void InsertTextCommand::redo() void InsertTextCommand::redo()
{ {
auto new_cursor = m_document.insert_at(m_range.start(), m_text); auto new_cursor = m_document.insert_at(m_range.start(), m_text, m_client);
// NOTE: We don't know where the range ends until after doing redo(). // NOTE: We don't know where the range ends until after doing redo().
// This is okay since we always do redo() after adding this to the undo stack. // This is okay since we always do redo() after adding this to the undo stack.
m_range.set_end(new_cursor); m_range.set_end(new_cursor);
@ -464,26 +465,25 @@ void GTextDocument::update_undo_timer()
m_undo_stack.finalize_current_combo(); m_undo_stack.finalize_current_combo();
} }
GTextPosition GTextDocument::insert_at(const GTextPosition& position, const StringView& text) GTextPosition GTextDocument::insert_at(const GTextPosition& position, const StringView& text, const Client* client)
{ {
GTextPosition cursor = position; GTextPosition cursor = position;
for (size_t i = 0; i < text.length(); ++i) for (size_t i = 0; i < text.length(); ++i)
cursor = insert_at(cursor, text[i]); cursor = insert_at(cursor, text[i], client);
return cursor; return cursor;
} }
GTextPosition GTextDocument::insert_at(const GTextPosition& position, char ch) GTextPosition GTextDocument::insert_at(const GTextPosition& position, char ch, const Client* client)
{ {
// FIXME: We need these from GTextEditor! bool automatic_indentation_enabled = client ? client->is_automatic_indentation_enabled() : false;
bool m_automatic_indentation_enabled = true; size_t m_soft_tab_width = client ? client->soft_tab_width() : 4;
size_t m_soft_tab_width = 4;
bool at_head = position.column() == 0; bool at_head = position.column() == 0;
bool at_tail = position.column() == line(position.line()).length(); bool at_tail = position.column() == line(position.line()).length();
if (ch == '\n') { if (ch == '\n') {
if (at_tail || at_head) { if (at_tail || at_head) {
String new_line_contents; String new_line_contents;
if (m_automatic_indentation_enabled && at_tail) { if (automatic_indentation_enabled && at_tail) {
size_t leading_spaces = 0; size_t leading_spaces = 0;
auto& old_line = lines()[position.line()]; auto& old_line = lines()[position.line()];
for (size_t i = 0; i < old_line.length(); ++i) { for (size_t i = 0; i < old_line.length(); ++i) {

View file

@ -40,6 +40,7 @@
class GTextEditor; class GTextEditor;
class GTextDocument; class GTextDocument;
class GTextDocumentLine; class GTextDocumentLine;
class GTextDocumentUndoCommand;
struct GTextDocumentSpan { struct GTextDocumentSpan {
GTextRange range; GTextRange range;
@ -50,37 +51,6 @@ struct GTextDocumentSpan {
void* data { nullptr }; void* data { nullptr };
}; };
class GTextDocumentUndoCommand : public GCommand {
public:
GTextDocumentUndoCommand(GTextDocument&);
virtual ~GTextDocumentUndoCommand();
protected:
GTextDocument& m_document;
};
class InsertTextCommand : public GTextDocumentUndoCommand {
public:
InsertTextCommand(GTextDocument&, const String&, const GTextPosition&);
virtual void undo() override;
virtual void redo() override;
private:
String m_text;
GTextRange m_range;
};
class RemoveTextCommand : public GTextDocumentUndoCommand {
public:
RemoveTextCommand(GTextDocument&, const String&, const GTextRange&);
virtual void undo() override;
virtual void redo() override;
private:
String m_text;
GTextRange m_range;
};
class GTextDocument : public RefCounted<GTextDocument> { class GTextDocument : public RefCounted<GTextDocument> {
public: public:
enum class SearchShouldWrap { enum class SearchShouldWrap {
@ -98,6 +68,9 @@ public:
virtual void document_did_change() = 0; virtual void document_did_change() = 0;
virtual void document_did_set_text() = 0; virtual void document_did_set_text() = 0;
virtual void document_did_set_cursor(const GTextPosition&) = 0; virtual void document_did_set_cursor(const GTextPosition&) = 0;
virtual bool is_automatic_indentation_enabled() const = 0;
virtual int soft_tab_width() const = 0;
}; };
static NonnullRefPtr<GTextDocument> create(Client* client = nullptr) static NonnullRefPtr<GTextDocument> create(Client* client = nullptr)
@ -157,8 +130,8 @@ public:
void notify_did_change(); void notify_did_change();
void set_all_cursors(const GTextPosition&); void set_all_cursors(const GTextPosition&);
GTextPosition insert_at(const GTextPosition&, char); GTextPosition insert_at(const GTextPosition&, char, const Client* = nullptr);
GTextPosition insert_at(const GTextPosition&, const StringView&); GTextPosition insert_at(const GTextPosition&, const StringView&, const Client* = nullptr);
void remove(const GTextRange&); void remove(const GTextRange&);
private: private:
@ -201,3 +174,42 @@ private:
// NOTE: This vector is null terminated. // NOTE: This vector is null terminated.
Vector<char> m_text; Vector<char> m_text;
}; };
class GTextDocumentUndoCommand : public GCommand {
public:
GTextDocumentUndoCommand(GTextDocument&);
virtual ~GTextDocumentUndoCommand();
void execute_from(const GTextDocument::Client& client)
{
m_client = &client;
redo();
m_client = nullptr;
}
protected:
GTextDocument& m_document;
const GTextDocument::Client* m_client { nullptr };
};
class InsertTextCommand : public GTextDocumentUndoCommand {
public:
InsertTextCommand(GTextDocument&, const String&, const GTextPosition&);
virtual void undo() override;
virtual void redo() override;
private:
String m_text;
GTextRange m_range;
};
class RemoveTextCommand : public GTextDocumentUndoCommand {
public:
RemoveTextCommand(GTextDocument&, const String&, const GTextRange&);
virtual void undo() override;
virtual void redo() override;
private:
String m_text;
GTextRange m_range;
};

View file

@ -60,9 +60,11 @@ public:
bool is_readonly() const { return m_readonly; } bool is_readonly() const { return m_readonly; }
void set_readonly(bool); void set_readonly(bool);
bool is_automatic_indentation_enabled() const { return m_automatic_indentation_enabled; } virtual bool is_automatic_indentation_enabled() const final { return m_automatic_indentation_enabled; }
void set_automatic_indentation_enabled(bool enabled) { m_automatic_indentation_enabled = enabled; } void set_automatic_indentation_enabled(bool enabled) { m_automatic_indentation_enabled = enabled; }
virtual int soft_tab_width() const final { return m_soft_tab_width; }
bool is_line_wrapping_enabled() const { return m_line_wrapping_enabled; } bool is_line_wrapping_enabled() const { return m_line_wrapping_enabled; }
void set_line_wrapping_enabled(bool); void set_line_wrapping_enabled(bool);
@ -202,7 +204,7 @@ private:
inline void execute(Args&&... args) inline void execute(Args&&... args)
{ {
auto command = make<T>(*m_document, forward<Args>(args)...); auto command = make<T>(*m_document, forward<Args>(args)...);
command->redo(); command->execute_from(*this);
m_document->add_to_undo_stack(move(command)); m_document->add_to_undo_stack(move(command));
} }