diff options
author | Zac <zacary.gillerat@connect.qut.edu.au> | 2021-01-27 21:57:39 +1000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-28 08:17:02 +0100 |
commit | aaf691c4efc1e43e83f13cdf403f69d89f889d38 (patch) | |
tree | dda81078884f650040a41dfc9b484ca9007e186d /Userland/Libraries | |
parent | e2d7945e0c7f6f5ff2743405a4b2378c37c7f5b6 (diff) | |
download | serenity-aaf691c4efc1e43e83f13cdf403f69d89f889d38.zip |
Vim: Add change word and delete word functionality
Add the functionality of key sequences 'cw', 'ce', 'cb', 'dw', 'de' and 'db'.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibGUI/EditingEngine.cpp | 76 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/EditingEngine.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextEditor.cpp | 9 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextEditor.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/VimEditingEngine.cpp | 66 |
5 files changed, 129 insertions, 28 deletions
diff --git a/Userland/Libraries/LibGUI/EditingEngine.cpp b/Userland/Libraries/LibGUI/EditingEngine.cpp index 42c3aeb132..bb82a9a6a3 100644 --- a/Userland/Libraries/LibGUI/EditingEngine.cpp +++ b/Userland/Libraries/LibGUI/EditingEngine.cpp @@ -387,7 +387,7 @@ void EditingEngine::get_selection_line_boundaries(size_t& first_line, size_t& la last_line -= 1; } -void EditingEngine::move_to_beginning_of_next_word() +TextPosition EditingEngine::find_beginning_of_next_word() { /* The rules that have been coded in: * Jump to the next punct or alnum after any whitespace @@ -413,7 +413,7 @@ void EditingEngine::move_to_beginning_of_next_word() auto& line = lines.at(line_index); if (line.is_empty() && !is_first_line) { - return m_editor->set_cursor({ line_index, 0 }); + return { line_index, 0 }; } else if (line.is_empty()) { has_seen_whitespace = true; } @@ -427,24 +427,22 @@ void EditingEngine::move_to_beginning_of_next_word() const u32 current_char = line_chars[column_index]; if (started_on_punct && vim_isalnum(current_char)) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } if (vim_ispunct(current_char) && !started_on_punct) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } if (isspace(current_char)) has_seen_whitespace = true; if (has_seen_whitespace && (vim_isalnum(current_char) || vim_ispunct(current_char))) { - m_editor->set_cursor({ line_index, column_index }); - return; + return { line_index, column_index }; } if (line_index == lines.size() - 1 && column_index == line.length() - 1) { - m_editor->set_cursor({ line_index, column_index }); - return; + return { line_index, column_index }; } // Implicit newline @@ -452,11 +450,16 @@ void EditingEngine::move_to_beginning_of_next_word() has_seen_whitespace = true; } } + ASSERT_NOT_REACHED(); } -void EditingEngine::move_to_end_of_next_word() +void EditingEngine::move_to_beginning_of_next_word() { + m_editor->set_cursor(find_beginning_of_next_word()); +} +TextPosition EditingEngine::find_end_of_next_word() +{ /* The rules that have been coded in: * If the current_char is alnum and the next is whitespace or punct * If the current_char is punct and the next is whitespace or alnum @@ -479,7 +482,7 @@ void EditingEngine::move_to_end_of_next_word() auto& line = lines.at(line_index); if (line.is_empty() && !is_first_line) { - return m_editor->set_cursor({ line_index, 0 }); + return { line_index, 0 }; } is_first_line = false; @@ -492,7 +495,7 @@ void EditingEngine::move_to_end_of_next_word() const u32 current_char = line_chars[column_index]; if (column_index == lines.at(line_index).length() - 1 && !is_first_iteration && (vim_isalnum(current_char) || vim_ispunct(current_char))) - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; else if (column_index == lines.at(line_index).length() - 1) { is_first_iteration = false; continue; @@ -501,21 +504,28 @@ void EditingEngine::move_to_end_of_next_word() const u32 next_char = line_chars[column_index + 1]; if (!is_first_iteration && vim_isalnum(current_char) && (isspace(next_char) || vim_ispunct(next_char))) - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; if (!is_first_iteration && vim_ispunct(current_char) && (isspace(next_char) || vim_isalnum(next_char))) - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; if (line_index == lines.size() - 1 && column_index == line.length() - 1) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } is_first_iteration = false; } } + ASSERT_NOT_REACHED(); } -void EditingEngine::move_to_end_of_previous_word() +void EditingEngine::move_to_end_of_next_word() +{ + + m_editor->set_cursor(find_end_of_next_word()); +} + +TextPosition EditingEngine::find_end_of_previous_word() { auto vim_isalnum = [](int c) { return c == '_' || isalnum(c); @@ -534,7 +544,7 @@ void EditingEngine::move_to_end_of_previous_word() auto& line = lines.at(line_index); if (line.is_empty() && !is_first_line) { - return m_editor->set_cursor({ line_index, 0 }); + return { line_index, 0 }; } else if (line.is_empty()) { has_seen_whitespace = true; } @@ -550,11 +560,11 @@ void EditingEngine::move_to_end_of_previous_word() const u32 current_char = line_chars[column_index]; if (started_on_punct && vim_isalnum(current_char)) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } if (vim_ispunct(current_char) && !started_on_punct) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } if (isspace(current_char)) { @@ -562,13 +572,11 @@ void EditingEngine::move_to_end_of_previous_word() } if (has_seen_whitespace && (vim_isalnum(current_char) || vim_ispunct(current_char))) { - m_editor->set_cursor({ line_index, column_index }); - return; + return { line_index, column_index }; } if (line_index == 0 && column_index == 0) { - m_editor->set_cursor({ line_index, column_index }); - return; + return { line_index, column_index }; } // Implicit newline when wrapping back up to the end of the previous line. @@ -576,9 +584,15 @@ void EditingEngine::move_to_end_of_previous_word() has_seen_whitespace = true; } } + ASSERT_NOT_REACHED(); } -void EditingEngine::move_to_beginning_of_previous_word() +void EditingEngine::move_to_end_of_previous_word() +{ + m_editor->set_cursor(find_end_of_previous_word()); +} + +TextPosition EditingEngine::find_beginning_of_previous_word() { auto vim_isalnum = [](int c) { return c == '_' || isalnum(c); @@ -596,7 +610,7 @@ void EditingEngine::move_to_beginning_of_previous_word() auto& line = lines.at(line_index); if (line.is_empty() && !is_first_iterated_line) { - return m_editor->set_cursor({ line_index, 0 }); + return { line_index, 0 }; } is_first_iterated_line = false; @@ -615,9 +629,9 @@ void EditingEngine::move_to_beginning_of_previous_word() const u32 current_char = line_chars[column_index]; if (column_index == 0 && !is_first_iteration && (vim_isalnum(current_char) || vim_ispunct(current_char))) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } else if (line_index == 0 && column_index == 0) { - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; } else if (column_index == 0 && is_first_iteration) { is_first_iteration = false; continue; @@ -626,14 +640,20 @@ void EditingEngine::move_to_beginning_of_previous_word() const u32 next_char = line_chars[column_index - 1]; if (!is_first_iteration && vim_isalnum(current_char) && (isspace(next_char) || vim_ispunct(next_char))) - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; if (!is_first_iteration && vim_ispunct(current_char) && (isspace(next_char) || vim_isalnum(next_char))) - return m_editor->set_cursor({ line_index, column_index }); + return { line_index, column_index }; is_first_iteration = false; } } + ASSERT_NOT_REACHED(); +} + +void EditingEngine::move_to_beginning_of_previous_word() +{ + m_editor->set_cursor(find_beginning_of_previous_word()); } void EditingEngine::move_selected_lines_up() diff --git a/Userland/Libraries/LibGUI/EditingEngine.h b/Userland/Libraries/LibGUI/EditingEngine.h index b8f65b4113..a9eb525acf 100644 --- a/Userland/Libraries/LibGUI/EditingEngine.h +++ b/Userland/Libraries/LibGUI/EditingEngine.h @@ -68,9 +68,13 @@ protected: void move_page_down(const KeyEvent& event); void move_to_first_line(); void move_to_last_line(); + TextPosition find_beginning_of_next_word(); void move_to_beginning_of_next_word(); + TextPosition find_end_of_next_word(); void move_to_end_of_next_word(); + TextPosition find_end_of_previous_word(); void move_to_end_of_previous_word(); + TextPosition find_beginning_of_previous_word(); void move_to_beginning_of_previous_word(); void move_up(const KeyEvent& event, double page_height_factor); diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index d4b916b994..55be2949e6 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -1130,6 +1130,15 @@ void TextEditor::delete_selection() update(); } +void TextEditor::delete_text_range(TextRange range) +{ + auto normalized_range = range.normalized(); + execute<RemoveTextCommand>(document().text_in_range(normalized_range), normalized_range); + did_change(); + set_cursor(normalized_range.start()); + update(); +} + void TextEditor::insert_at_cursor_or_replace_selection(const StringView& text) { ReflowDeferrer defer(*this); diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index 05ddf20383..956e3cfcce 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -193,6 +193,8 @@ public: Gfx::IntRect cursor_content_rect() const; TextPosition text_position_at_content_position(const Gfx::IntPoint&) const; + void delete_text_range(TextRange); + protected: explicit TextEditor(Type = Type::MultiLine); diff --git a/Userland/Libraries/LibGUI/VimEditingEngine.cpp b/Userland/Libraries/LibGUI/VimEditingEngine.cpp index db0990cc0c..60a6a2b21b 100644 --- a/Userland/Libraries/LibGUI/VimEditingEngine.cpp +++ b/Userland/Libraries/LibGUI/VimEditingEngine.cpp @@ -65,10 +65,41 @@ bool VimEditingEngine::on_key_in_insert_mode(const KeyEvent& event) bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) { + // FIXME: changing or deleting word methods don't correctly support 1 letter words. + // For example, in the line 'return 0;' with the cursor on the '0', + // keys 'cw' will move to the delete '0;' rather than just the '0'. + + // FIXME: Changing or deleting the last word on a line will bring the next line + // up to the cursor. if (m_previous_key == KeyCode::Key_D) { if (event.key() == KeyCode::Key_D) { yank(Line); delete_line(); + } else if (event.key() == KeyCode::Key_W) { + // If the current char is an alnum or punct, delete from said char, to the + // beginning of the next word including any whitespace in between the two words. + // If the current char is whitespace, delete from the cursor to the beginning of the next world. + u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()]; + TextPosition delete_to; + if (isspace(current_char)) { + delete_to = find_beginning_of_next_word(); + } else { + delete_to = find_end_of_next_word(); + delete_to.set_column(delete_to.column() + 1); + } + m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized()); + } else if (event.key() == KeyCode::Key_B) { + // Will delete from the current char to the beginning of the previous word regardless of whitespace. + // Does not delete the starting char, see note below. + TextPosition delete_to = find_beginning_of_previous_word(); + // NOTE: Intentionally don't adjust m_editor->cursor() for the wide cursor's column + // because in the original vim... they don't. + m_editor->delete_text_range(TextRange(delete_to, m_editor->cursor()).normalized()); + } else if (event.key() == KeyCode::Key_E) { + // Delete from the current char to the end of the next word regardless of whitespace. + TextPosition delete_to = find_end_of_next_word(); + delete_to.set_column(delete_to.column() + 1); + m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized()); } m_previous_key = {}; } else if (m_previous_key == KeyCode::Key_G) { @@ -102,6 +133,41 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) m_editor->add_code_point(0x0A); } switch_to_insert_mode(); + } else if (event.key() == KeyCode::Key_W) { + // Delete to the end of the next word, if in the middle of a word, this will delete + // from the cursor to the said of said word. If the cursor is on whitespace, + // any whitespace between the cursor and the beginning of the next word will be deleted. + u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()]; + TextPosition delete_to; + if (isspace(current_char)) { + delete_to = find_beginning_of_next_word(); + } else { + delete_to = find_end_of_next_word(); + delete_to.set_column(delete_to.column() + 1); + } + m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized()); + switch_to_insert_mode(); + } else if (event.key() == KeyCode::Key_B) { + // Delete to the beginning of the previous word, if in the middle of a word, this will delete + // from the cursor to the beginning of said word. If the cursor is on whitespace + // it, and the previous word will be deleted. + u32 current_char = m_editor->current_line().view().code_points()[m_editor->cursor().column()]; + TextPosition delete_to = find_beginning_of_previous_word(); + TextPosition adjusted_cursor = m_editor->cursor(); + // Adjust cursor for the column the wide cursor is covering + if (isalnum(current_char) || ispunct(current_char)) + adjusted_cursor.set_column(adjusted_cursor.column() + 1); + m_editor->delete_text_range(TextRange(delete_to, adjusted_cursor).normalized()); + switch_to_insert_mode(); + } else if (event.key() == KeyCode::Key_E) { + // Delete to the end of the next word, if in the middle of a word, this will delete + // from the cursor to the end of said word. If the cursor is on whitespace + // it, and the next word will be deleted. + TextPosition delete_to = find_end_of_next_word(); + TextPosition adjusted_cursor = m_editor->cursor(); + delete_to.set_column(delete_to.column() + 1); + m_editor->delete_text_range(TextRange(adjusted_cursor, delete_to).normalized()); + switch_to_insert_mode(); } m_previous_key = {}; } else { |