diff options
author | huttongrabiel <huttonthomas@icloud.com> | 2022-06-18 12:02:51 -0700 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2022-07-08 11:47:56 +0100 |
commit | 2fbaa7996c9f9fdbfcebf3af95fab88a89de62ac (patch) | |
tree | 082799b85b6dacd387c798328c6e2f754f3670d7 /Userland/Libraries | |
parent | 80705a72bde015c824126ea37e55337439b89b76 (diff) | |
download | serenity-2fbaa7996c9f9fdbfcebf3af95fab88a89de62ac.zip |
LibGUI: Indent selected text on tab press
If selected text is less than a whole line, usual delete/replace takes
place. Otherwise, if the selected text is a whole line or spans
multiple lines, the selection will be indented.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibGUI/TextDocument.cpp | 27 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextDocument.h | 12 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextEditor.cpp | 41 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextEditor.h | 2 |
4 files changed, 80 insertions, 2 deletions
diff --git a/Userland/Libraries/LibGUI/TextDocument.cpp b/Userland/Libraries/LibGUI/TextDocument.cpp index 10a8f76cb3..cc0f60d1ad 100644 --- a/Userland/Libraries/LibGUI/TextDocument.cpp +++ b/Userland/Libraries/LibGUI/TextDocument.cpp @@ -932,6 +932,33 @@ String ReplaceAllTextCommand::action_text() const return m_action_text; } +IndentSelection::IndentSelection(TextDocument& document, size_t tab_width, TextRange const& range) + : TextDocumentUndoCommand(document) + , m_tab_width(tab_width) + , m_range(range) +{ +} + +void IndentSelection::redo() +{ + auto const tab = String::repeated(' ', m_tab_width); + + for (size_t i = m_range.start().line(); i <= m_range.end().line(); i++) { + m_document.insert_at({ i, 0 }, tab, m_client); + } + + m_document.set_all_cursors(m_range.start()); +} + +void IndentSelection::undo() +{ + for (size_t i = m_range.start().line(); i <= m_range.end().line(); i++) { + m_document.remove({ { i, 0 }, { i, m_tab_width } }); + } + + m_document.set_all_cursors(m_range.start()); +} + TextPosition TextDocument::insert_at(TextPosition const& position, StringView text, Client const* client) { TextPosition cursor = position; diff --git a/Userland/Libraries/LibGUI/TextDocument.h b/Userland/Libraries/LibGUI/TextDocument.h index dc24141fab..32b28e3292 100644 --- a/Userland/Libraries/LibGUI/TextDocument.h +++ b/Userland/Libraries/LibGUI/TextDocument.h @@ -259,4 +259,16 @@ private: String m_action_text; }; +class IndentSelection : public TextDocumentUndoCommand { +public: + IndentSelection(TextDocument&, size_t tab_width, TextRange const&); + virtual void undo() override; + virtual void redo() override; + TextRange const& range() const { return m_range; } + +private: + size_t m_tab_width { 0 }; + TextRange m_range; +}; + } diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index aa91b8dfa9..f61aabff25 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -867,6 +867,15 @@ void TextEditor::keydown_event(KeyEvent& event) return; } + if (event.key() == KeyCode::Key_Tab) { + if (has_selection()) { + if (is_indenting_selection()) { + indent_selection(); + return; + } + } + } + if (event.key() == KeyCode::Key_Delete) { if (!is_editable()) return; @@ -952,6 +961,33 @@ void TextEditor::keydown_event(KeyEvent& event) event.ignore(); } +bool TextEditor::is_indenting_selection() +{ + auto const selection_start = m_selection.start() > m_selection.end() ? m_selection.end() : m_selection.start(); + auto const selection_end = m_selection.end() > m_selection.start() ? m_selection.end() : m_selection.start(); + auto const whole_line_selected = selection_end.column() - selection_start.column() >= current_line().length() - current_line().first_non_whitespace_column(); + auto const on_same_line = selection_start.line() == selection_end.line(); + + if (has_selection() && (whole_line_selected || !on_same_line)) { + return true; + } + + return false; +} + +void TextEditor::indent_selection() +{ + auto const selection_start = m_selection.start() > m_selection.end() ? m_selection.end() : m_selection.start(); + auto const selection_end = m_selection.end() > m_selection.start() ? m_selection.end() : m_selection.start(); + + if (is_indenting_selection()) { + execute<IndentSelection>(m_soft_tab_width, TextRange(selection_start, selection_end)); + m_selection.set_start({ selection_start.line(), selection_start.column() + m_soft_tab_width }); + m_selection.set_end({ selection_end.line(), selection_end.column() + m_soft_tab_width }); + set_cursor({ m_cursor.line(), m_cursor.column() + m_soft_tab_width }); + } +} + void TextEditor::delete_previous_word() { TextRange to_erase(document().first_word_before(m_cursor, true), m_cursor); @@ -1444,7 +1480,7 @@ void TextEditor::insert_at_cursor_or_replace_selection(StringView text) { ReflowDeferrer defer(*this); VERIFY(is_editable()); - if (has_selection()) + if (has_selection() && !is_indenting_selection()) delete_selection(); // Check if adding a newline leaves the previous line as just whitespace. @@ -1453,7 +1489,8 @@ void TextEditor::insert_at_cursor_or_replace_selection(StringView text) && clear_length > 0 && current_line().leading_spaces() == clear_length; - execute<InsertTextCommand>(text, m_cursor); + if (!is_indenting_selection()) + execute<InsertTextCommand>(text, m_cursor); if (should_clear_last_line) { // If it does leave just whitespace, clear it. auto const original_cursor_position = cursor(); diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index dfcbbcc52b..53865c831d 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -151,6 +151,8 @@ public: void select_current_line(); virtual void undo(); virtual void redo(); + bool is_indenting_selection(); + void indent_selection(); Function<void()> on_change; Function<void(bool modified)> on_modified_change; |