summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorZac <zacary.gillerat@connect.qut.edu.au>2021-01-27 21:57:39 +1000
committerAndreas Kling <kling@serenityos.org>2021-01-28 08:17:02 +0100
commitaaf691c4efc1e43e83f13cdf403f69d89f889d38 (patch)
treedda81078884f650040a41dfc9b484ca9007e186d /Userland/Libraries
parente2d7945e0c7f6f5ff2743405a4b2378c37c7f5b6 (diff)
downloadserenity-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.cpp76
-rw-r--r--Userland/Libraries/LibGUI/EditingEngine.h4
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.cpp9
-rw-r--r--Userland/Libraries/LibGUI/TextEditor.h2
-rw-r--r--Userland/Libraries/LibGUI/VimEditingEngine.cpp66
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 {