diff options
author | Zac <zacary.gillerat@connect.qut.edu.au> | 2021-01-25 04:24:56 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-24 19:24:56 +0100 |
commit | 330ab52ddb8befa74a18a86ee53c033e1267631a (patch) | |
tree | a7ae9730b65bd02d0c11045935aa284350c18552 | |
parent | 0678dab7dca0bba07e9370140637cbac64625a42 (diff) | |
download | serenity-330ab52ddb8befa74a18a86ee53c033e1267631a.zip |
Vim: More correct word jumping (#5090)
Implemented move_to_beginning_of_next(), move_to_end_of_next(),
move_to_beginning_of_previous() and move_to_end_of_previous() functions
for more correct word jumping than the move_to_xxx_span() methods that
were previously used.
-rw-r--r-- | Userland/Libraries/LibGUI/EditingEngine.cpp | 251 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/EditingEngine.h | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/VimEditingEngine.cpp | 22 |
3 files changed, 273 insertions, 4 deletions
diff --git a/Userland/Libraries/LibGUI/EditingEngine.cpp b/Userland/Libraries/LibGUI/EditingEngine.cpp index 4cd0251a13..b49b3031c0 100644 --- a/Userland/Libraries/LibGUI/EditingEngine.cpp +++ b/Userland/Libraries/LibGUI/EditingEngine.cpp @@ -387,6 +387,257 @@ void EditingEngine::get_selection_line_boundaries(size_t& first_line, size_t& la last_line -= 1; } +void EditingEngine::move_to_beginning_of_next_word() +{ + /* The rules that have been coded in: + * Jump to the next punct or alnum after any whitespace + * Jump to the next non-consecutive punct regardless of whitespace + * Jump to the next alnum if started on punct regardless of whitespace + * If the end of the input is reached, jump there + */ + + auto vim_isalnum = [](int c) { + return c == '_' || isalnum(c); + }; + + auto vim_ispunct = [](int c) { + return c != '_' && ispunct(c); + }; + + bool started_on_punct = vim_ispunct(m_editor->current_line().to_utf8().characters()[m_editor->cursor().column()]); + bool has_seen_whitespace = false; + bool is_first_line = true; + auto& lines = m_editor->lines(); + auto cursor = m_editor->cursor(); + for (size_t line_index = cursor.line(); line_index < lines.size(); line_index++) { + auto& line = lines.at(line_index); + + if (line.is_empty() && !is_first_line) { + return m_editor->set_cursor({ line_index, 0 }); + } else if (line.is_empty()) { + has_seen_whitespace = true; + } + + is_first_line = false; + + for (size_t column_index = 0; column_index < lines.at(line_index).length(); column_index++) { + if (line_index == cursor.line() && column_index < cursor.column()) + continue; + const u32* line_chars = line.view().code_points(); + 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 }); + } + + if (vim_ispunct(current_char) && !started_on_punct) { + return m_editor->set_cursor({ 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; + } + + if (line_index == lines.size() - 1 && column_index == line.length() - 1) { + m_editor->set_cursor({ line_index, column_index }); + return; + } + + // Implicit newline + if (column_index == line.length() - 1) + has_seen_whitespace = true; + } + } +} + +void EditingEngine::move_to_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 + * If the end of the input is reached, jump there + */ + + auto vim_isalnum = [](int c) { + return c == '_' || isalnum(c); + }; + + auto vim_ispunct = [](int c) { + return c != '_' && ispunct(c); + }; + + bool is_first_line = true; + bool is_first_iteration = true; + auto& lines = m_editor->lines(); + auto cursor = m_editor->cursor(); + for (size_t line_index = cursor.line(); line_index < lines.size(); line_index++) { + auto& line = lines.at(line_index); + + if (line.is_empty() && !is_first_line) { + return m_editor->set_cursor({ line_index, 0 }); + } + + is_first_line = false; + + for (size_t column_index = 0; column_index < lines.at(line_index).length(); column_index++) { + if (line_index == cursor.line() && column_index < cursor.column()) + continue; + + const u32* line_chars = line.view().code_points(); + 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 }); + else if (column_index == lines.at(line_index).length() - 1) { + is_first_iteration = false; + continue; + } + + 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 }); + + if (!is_first_iteration && vim_ispunct(current_char) && (isspace(next_char) || vim_isalnum(next_char))) + return m_editor->set_cursor({ line_index, column_index }); + + if (line_index == lines.size() - 1 && column_index == line.length() - 1) { + return m_editor->set_cursor({ line_index, column_index }); + } + + is_first_iteration = false; + } + } +} + +void EditingEngine::move_to_end_of_previous_word() +{ + auto vim_isalnum = [](int c) { + return c == '_' || isalnum(c); + }; + + auto vim_ispunct = [](int c) { + return c != '_' && ispunct(c); + }; + + bool started_on_punct = vim_ispunct(m_editor->current_line().to_utf8().characters()[m_editor->cursor().column()]); + bool is_first_line = true; + bool has_seen_whitespace = false; + auto& lines = m_editor->lines(); + auto cursor = m_editor->cursor(); + for (size_t line_index = cursor.line(); (int)line_index >= 0; line_index--) { + auto& line = lines.at(line_index); + + if (line.is_empty() && !is_first_line) { + return m_editor->set_cursor({ line_index, 0 }); + } else if (line.is_empty()) { + has_seen_whitespace = true; + } + + is_first_line = false; + + size_t line_length = lines.at(line_index).length(); + for (size_t column_index = line_length - 1; (int)column_index >= 0; column_index--) { + if (line_index == cursor.line() && column_index > cursor.column()) + continue; + + const u32* line_chars = line.view().code_points(); + 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 }); + } + + if (vim_ispunct(current_char) && !started_on_punct) { + return m_editor->set_cursor({ 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; + } + + if (line_index == lines.size() - 1 && column_index == line.length() - 1) { + m_editor->set_cursor({ line_index, column_index }); + return; + } + + // Implicit newline when wrapping back up to the end of the previous line. + if (column_index == 0) + has_seen_whitespace = true; + } + } +} + +void EditingEngine::move_to_beginning_of_previous_word() +{ + auto vim_isalnum = [](int c) { + return c == '_' || isalnum(c); + }; + + auto vim_ispunct = [](int c) { + return c != '_' && ispunct(c); + }; + + bool is_first_iterated_line = true; + bool is_first_iteration = true; + auto& lines = m_editor->lines(); + auto cursor = m_editor->cursor(); + for (size_t line_index = cursor.line(); (int)line_index >= 0; line_index--) { + auto& line = lines.at(line_index); + + if (line.is_empty() && !is_first_iterated_line) { + return m_editor->set_cursor({ line_index, 0 }); + } + + is_first_iterated_line = false; + + size_t line_length = lines.at(line_index).length(); + for (size_t column_index = line_length; (int)column_index >= 0; column_index--) { + if (line_index == cursor.line() && column_index > cursor.column()) + continue; + + if (column_index == line_length) { + is_first_iteration = false; + continue; + } + + const u32* line_chars = line.view().code_points(); + 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 }); + else if (column_index == 0) { + is_first_iteration = false; + continue; + } + + 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 }); + + if (!is_first_iteration && vim_ispunct(current_char) && (isspace(next_char) || vim_isalnum(next_char))) + return m_editor->set_cursor({ line_index, column_index }); + + if (line_index == lines.size() - 1 && column_index == line.length() - 1) { + return m_editor->set_cursor({ line_index, column_index }); + } + + is_first_iteration = false; + } + } +} + void EditingEngine::move_selected_lines_up() { if (!m_editor->is_editable()) diff --git a/Userland/Libraries/LibGUI/EditingEngine.h b/Userland/Libraries/LibGUI/EditingEngine.h index 37c25410ce..b8f65b4113 100644 --- a/Userland/Libraries/LibGUI/EditingEngine.h +++ b/Userland/Libraries/LibGUI/EditingEngine.h @@ -68,6 +68,10 @@ protected: void move_page_down(const KeyEvent& event); void move_to_first_line(); void move_to_last_line(); + void move_to_beginning_of_next_word(); + void move_to_end_of_next_word(); + void move_to_end_of_previous_word(); + void move_to_beginning_of_previous_word(); void move_up(const KeyEvent& event, double page_height_factor); void move_down(const KeyEvent& event, double page_height_factor); diff --git a/Userland/Libraries/LibGUI/VimEditingEngine.cpp b/Userland/Libraries/LibGUI/VimEditingEngine.cpp index 3cd80ad5fe..6a49a20d1a 100644 --- a/Userland/Libraries/LibGUI/VimEditingEngine.cpp +++ b/Userland/Libraries/LibGUI/VimEditingEngine.cpp @@ -73,6 +73,8 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) } else if (m_previous_key == KeyCode::Key_G) { if (event.key() == KeyCode::Key_G) { move_to_first_line(); + } else if (event.key() == KeyCode::Key_E) { + move_to_end_of_previous_word(); } m_previous_key = {}; } else { @@ -139,7 +141,7 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) switch_to_insert_mode(); break; case (KeyCode::Key_B): - move_to_previous_span(event); // FIXME: This probably isn't 100% correct. + move_to_beginning_of_previous_word(); break; case (KeyCode::Key_Backspace): case (KeyCode::Key_H): @@ -147,6 +149,11 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) move_one_left(event); break; case (KeyCode::Key_D): + m_previous_key = event.key(); + break; + case (KeyCode::Key_E): + move_to_end_of_next_word(); + break; case (KeyCode::Key_G): m_previous_key = event.key(); break; @@ -174,7 +181,7 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event) m_editor->undo(); break; case (KeyCode::Key_W): - move_to_next_span(event); // FIXME: This probably isn't 100% correct. + move_to_beginning_of_next_word(); break; case (KeyCode::Key_X): delete_char(); @@ -203,6 +210,9 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event) if (event.key() == KeyCode::Key_G) { move_to_first_line(); update_selection_on_cursor_move(); + } else if (event.key() == KeyCode::Key_E) { + move_to_end_of_previous_word(); + update_selection_on_cursor_move(); } m_previous_key = {}; } else { @@ -260,7 +270,7 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event) if (!event.ctrl() && !event.shift() && !event.alt()) { switch (event.key()) { case (KeyCode::Key_B): - move_to_previous_span(event); // FIXME: This probably isn't 100% correct. + move_to_beginning_of_previous_word(); update_selection_on_cursor_move(); break; case (KeyCode::Key_Backspace): @@ -273,6 +283,10 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event) // TODO: Yank selected text m_editor->do_delete(); break; + case (KeyCode::Key_E): + move_to_end_of_next_word(); + update_selection_on_cursor_move(); + break; case (KeyCode::Key_G): m_previous_key = event.key(); break; @@ -295,7 +309,7 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event) // FIXME: Set selection to uppercase. break; case (KeyCode::Key_W): - move_to_next_span(event); // FIXME: This probably isn't 100% correct. + move_to_beginning_of_next_word(); update_selection_on_cursor_move(); break; case (KeyCode::Key_X): |