mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 15:40:19 +00:00
Vim: Add change word and delete word functionality
Add the functionality of key sequences 'cw', 'ce', 'cb', 'dw', 'de' and 'db'.
This commit is contained in:
parent
e2d7945e0c
commit
aaf691c4ef
Notes:
sideshowbarker
2024-07-18 22:48:02 +09:00
Author: https://github.com/supex0fan Commit: https://github.com/SerenityOS/serenity/commit/aaf691c4efc Pull-request: https://github.com/SerenityOS/serenity/pull/5135
5 changed files with 129 additions and 28 deletions
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue