Browse Source

LibGUI: Unindent selected text on shift+tab press

Selected text is unindented when Shift+Tab is pressed. Select text,
indent it with Tab, then unindent with Shift+Tab.
huttongrabiel 3 years ago
parent
commit
9369610bf4

+ 29 - 0
Userland/Libraries/LibGUI/TextDocument.cpp

@@ -959,6 +959,35 @@ void IndentSelection::undo()
     m_document.set_all_cursors(m_range.start());
 }
 
+UnindentSelection::UnindentSelection(TextDocument& document, size_t tab_width, TextRange const& range)
+    : TextDocumentUndoCommand(document)
+    , m_tab_width(tab_width)
+    , m_range(range)
+{
+}
+
+void UnindentSelection::redo()
+{
+    for (size_t i = m_range.start().line(); i <= m_range.end().line(); i++) {
+        if (m_document.line(i).leading_spaces() >= m_tab_width)
+            m_document.remove({ { i, 0 }, { i, m_tab_width } });
+        else
+            m_document.remove({ { i, 0 }, { i, m_document.line(i).leading_spaces() } });
+    }
+
+    m_document.set_all_cursors(m_range.start());
+}
+
+void UnindentSelection::undo()
+{
+    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());
+}
+
 TextPosition TextDocument::insert_at(TextPosition const& position, StringView text, Client const* client)
 {
     TextPosition cursor = position;

+ 12 - 0
Userland/Libraries/LibGUI/TextDocument.h

@@ -271,4 +271,16 @@ private:
     TextRange m_range;
 };
 
+class UnindentSelection : public TextDocumentUndoCommand {
+public:
+    UnindentSelection(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;
+};
+
 }

+ 21 - 0
Userland/Libraries/LibGUI/TextEditor.cpp

@@ -869,6 +869,10 @@ void TextEditor::keydown_event(KeyEvent& event)
 
     if (event.key() == KeyCode::Key_Tab) {
         if (has_selection()) {
+            if (event.modifiers() == Mod_Shift) {
+                unindent_selection();
+                return;
+            }
             if (is_indenting_selection()) {
                 indent_selection();
                 return;
@@ -988,6 +992,23 @@ void TextEditor::indent_selection()
     }
 }
 
+void TextEditor::unindent_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 (current_line().first_non_whitespace_column() != 0) {
+        if (current_line().first_non_whitespace_column() > m_soft_tab_width && selection_start.column() != 0) {
+            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 });
+        } else if (selection_start.column() != 0) {
+            m_selection.set_start({ selection_start.line(), selection_start.column() - current_line().leading_spaces() });
+            m_selection.set_end({ selection_end.line(), selection_end.column() - current_line().leading_spaces() });
+        }
+        execute<UnindentSelection>(m_soft_tab_width, TextRange(selection_start, selection_end));
+    }
+}
+
 void TextEditor::delete_previous_word()
 {
     TextRange to_erase(document().first_word_before(m_cursor, true), m_cursor);

+ 1 - 0
Userland/Libraries/LibGUI/TextEditor.h

@@ -153,6 +153,7 @@ public:
     virtual void redo();
     bool is_indenting_selection();
     void indent_selection();
+    void unindent_selection();
 
     Function<void()> on_change;
     Function<void(bool modified)> on_modified_change;