summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorhuttongrabiel <huttonthomas@icloud.com>2022-06-18 12:02:51 -0700
committerSam Atkins <atkinssj@gmail.com>2022-07-08 11:47:56 +0100
commit2fbaa7996c9f9fdbfcebf3af95fab88a89de62ac (patch)
tree082799b85b6dacd387c798328c6e2f754f3670d7 /Userland/Libraries
parent80705a72bde015c824126ea37e55337439b89b76 (diff)
downloadserenity-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.cpp27
-rw-r--r--Userland/Libraries/LibGUI/TextDocument.h12
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.cpp41
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.h2
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;