瀏覽代碼

LibGUI: Add Ctrl-W to insert mode

In Vim's insert mode, Ctrl-W deletes the word before the cursor, like
Ctrl-Backspace. Unlike Ctrl-Backspace, if only whitespace exists between
the end of the word and the cursor, the word will be deleted with the
whitespace.

To do so, this commit introduces two methods: delete_previous_word() for
TextEditor and first_word_before() for TextDocument, where the former
depends on the latter. delete_previous_word() is then called in
VimEditingEngine.
Ariel Don 4 年之前
父節點
當前提交
808e5e813f

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

@@ -674,6 +674,39 @@ TextPosition TextDocument::first_word_break_after(const TextPosition& position)
     return target;
 }
 
+TextPosition TextDocument::first_word_before(const TextPosition& position, bool start_at_column_before) const
+{
+    if (position.column() == 0) {
+        if (position.line() == 0) {
+            return TextPosition(0, 0);
+        }
+        auto previous_line = this->line(position.line() - 1);
+        return TextPosition(position.line() - 1, previous_line.length());
+    }
+
+    auto target = position;
+    auto line = this->line(target.line());
+    if (target.column() == line.length())
+        start_at_column_before = 1;
+
+    auto nonblank_passed = !is_ascii_blank(line.code_points()[target.column() - start_at_column_before]);
+    while (target.column() > 0) {
+        auto prev_code_point = line.code_points()[target.column() - 1];
+        nonblank_passed |= !is_ascii_blank(prev_code_point);
+
+        if (nonblank_passed && is_ascii_blank(prev_code_point)) {
+            break;
+        } else if (is_ascii_punctuation(prev_code_point)) {
+            target.set_column(target.column() - 1);
+            break;
+        }
+
+        target.set_column(target.column() - 1);
+    }
+
+    return target;
+}
+
 void TextDocument::undo()
 {
     if (!can_undo())

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

@@ -106,6 +106,8 @@ public:
     TextPosition first_word_break_before(const TextPosition&, bool start_at_column_before) const;
     TextPosition first_word_break_after(const TextPosition&) const;
 
+    TextPosition first_word_before(const TextPosition&, bool start_at_column_before) const;
+
     void add_to_undo_stack(NonnullOwnPtr<TextDocumentUndoCommand>);
 
     bool can_undo() const { return m_undo_stack.can_undo(); }

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

@@ -883,6 +883,12 @@ void TextEditor::keydown_event(KeyEvent& event)
     event.ignore();
 }
 
+void TextEditor::delete_previous_word()
+{
+    TextRange to_erase(document().first_word_before(m_cursor, true), m_cursor);
+    execute<RemoveTextCommand>(document().text_in_range(to_erase), to_erase);
+}
+
 void TextEditor::delete_current_line()
 {
     if (has_selection())

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

@@ -135,6 +135,7 @@ public:
     void paste();
     void do_delete();
     void delete_current_line();
+    void delete_previous_word();
     void select_all();
     virtual void undo();
     virtual void redo();

+ 10 - 0
Userland/Libraries/LibGUI/VimEditingEngine.cpp

@@ -794,6 +794,16 @@ bool VimEditingEngine::on_key_in_insert_mode(const KeyEvent& event)
     if (EditingEngine::on_key(event))
         return true;
 
+    if (event.ctrl()) {
+        switch (event.key()) {
+        case KeyCode::Key_W:
+            m_editor->delete_previous_word();
+            return true;
+        default:
+            break;
+        }
+    }
+
     if (event.key() == KeyCode::Key_Escape || (event.ctrl() && event.key() == KeyCode::Key_LeftBracket) || (event.ctrl() && event.key() == KeyCode::Key_C)) {
         if (m_editor->cursor().column() > 0)
             move_one_left();