diff --git a/Libraries/LibWeb/DOM/EditingHostManager.cpp b/Libraries/LibWeb/DOM/EditingHostManager.cpp index f96c97f7434..c3f7ecc2229 100644 --- a/Libraries/LibWeb/DOM/EditingHostManager.cpp +++ b/Libraries/LibWeb/DOM/EditingHostManager.cpp @@ -140,6 +140,18 @@ void EditingHostManager::decrement_cursor_position_to_previous_word(CollapseSele selection->move_offset_to_previous_word(collapse == CollapseSelection::Yes); } +void EditingHostManager::increment_cursor_position_to_next_line(CollapseSelection collapse) +{ + (void)collapse; + // FIXME: Implement this method +} + +void EditingHostManager::decrement_cursor_position_to_previous_line(CollapseSelection collapse) +{ + (void)collapse; + // FIXME: Implement this method +} + void EditingHostManager::handle_delete(DeleteDirection direction) { // https://w3c.github.io/editing/docs/execCommand/#additional-requirements diff --git a/Libraries/LibWeb/DOM/EditingHostManager.h b/Libraries/LibWeb/DOM/EditingHostManager.h index dad6225c096..e3ee9124a1f 100644 --- a/Libraries/LibWeb/DOM/EditingHostManager.h +++ b/Libraries/LibWeb/DOM/EditingHostManager.h @@ -35,6 +35,8 @@ public: virtual void decrement_cursor_position_offset(CollapseSelection) override; virtual void increment_cursor_position_to_next_word(CollapseSelection) override; virtual void decrement_cursor_position_to_previous_word(CollapseSelection) override; + virtual void increment_cursor_position_to_next_line(CollapseSelection) override; + virtual void decrement_cursor_position_to_previous_line(CollapseSelection) override; virtual void visit_edges(Cell::Visitor& visitor) override; diff --git a/Libraries/LibWeb/DOM/InputEventsTarget.h b/Libraries/LibWeb/DOM/InputEventsTarget.h index 0b8610303a7..4a894c43f09 100644 --- a/Libraries/LibWeb/DOM/InputEventsTarget.h +++ b/Libraries/LibWeb/DOM/InputEventsTarget.h @@ -40,6 +40,8 @@ public: virtual void decrement_cursor_position_offset(CollapseSelection) = 0; virtual void increment_cursor_position_to_next_word(CollapseSelection) = 0; virtual void decrement_cursor_position_to_previous_word(CollapseSelection) = 0; + virtual void increment_cursor_position_to_next_line(CollapseSelection) = 0; + virtual void decrement_cursor_position_to_previous_line(CollapseSelection) = 0; }; } diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index b7cab2fe652..66a61630be2 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -1019,6 +1019,89 @@ void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_wor selection_was_changed(); } +static constexpr size_t find_line_start(Utf16View const& view, size_t offset) +{ + while (offset != 0 && view.code_unit_at(offset - 1) != '\n') + --offset; + return offset; +} + +static constexpr size_t find_line_end(Utf16View const& view, size_t offset) +{ + auto length = view.length_in_code_units(); + while (offset < length && view.code_unit_at(offset) != '\n') + ++offset; + return offset; +} + +void FormAssociatedTextControlElement::increment_cursor_position_to_next_line(CollapseSelection collapse) +{ + auto const text_node = form_associated_element_to_text_node(); + if (!text_node) + return; + + auto code_points = text_node->data().utf16_view(); + auto length = code_points.length_in_code_units(); + auto current_line_end = find_line_end(code_points, m_selection_end); + + // initialize to handle the case of last line + size_t new_offset = current_line_end; + + if (current_line_end < length) { + auto next_line_start = current_line_end + 1; + auto position_within_line = m_selection_end - find_line_start(code_points, m_selection_end); + auto next_line_end = find_line_end(code_points, next_line_start); + auto next_line_length = next_line_end - next_line_start; + + new_offset = next_line_start + min(position_within_line, next_line_length); + + if (new_offset > 0 && new_offset < length && AK::UnicodeUtils::is_utf16_low_surrogate(code_points.code_unit_at(new_offset))) { + if (AK::UnicodeUtils::is_utf16_high_surrogate(code_points.code_unit_at(new_offset - 1))) + --new_offset; + } + } + + if (collapse == CollapseSelection::Yes) { + collapse_selection_to_offset(new_offset); + } else { + m_selection_end = new_offset; + } + + selection_was_changed(); +} + +void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_line(CollapseSelection collapse) +{ + auto const text_node = form_associated_element_to_text_node(); + if (!text_node) + return; + + auto code_points = text_node->data().utf16_view(); + size_t new_offset = 0; + + if (auto current_line_start = find_line_start(code_points, m_selection_end); current_line_start != 0) { + auto position_within_line = m_selection_end - current_line_start; + + auto previous_line_start = find_line_start(code_points, current_line_start - 1); + auto previous_line_length = current_line_start - previous_line_start - 1; + + new_offset = previous_line_start + min(position_within_line, previous_line_length); + + if (new_offset > 0 && AK::UnicodeUtils::is_utf16_low_surrogate(code_points.code_unit_at(new_offset))) { + if (AK::UnicodeUtils::is_utf16_high_surrogate(code_points.code_unit_at(new_offset - 1))) + --new_offset; + } + } + + if (collapse == CollapseSelection::Yes) { + collapse_selection_to_offset(new_offset); + } else { + m_selection_end = new_offset; + } + + selection_was_changed(); +} + GC::Ptr FormAssociatedTextControlElement::cursor_position() const { auto const node = form_associated_element_to_text_node(); diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index c96770a51f8..c93df33650e 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -240,6 +240,8 @@ public: virtual void decrement_cursor_position_offset(CollapseSelection) override; virtual void increment_cursor_position_to_next_word(CollapseSelection) override; virtual void decrement_cursor_position_to_previous_word(CollapseSelection) override; + virtual void increment_cursor_position_to_next_line(CollapseSelection) override; + virtual void decrement_cursor_position_to_previous_line(CollapseSelection) override; GC::Ptr cursor_position() const; diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index b0706841a31..cb80ba8261a 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -1321,6 +1321,16 @@ EventResult EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u return EventResult::Handled; } + if (key == UIEvents::KeyCode::Key_Up || key == UIEvents::KeyCode::Key_Down) { + auto collapse = modifiers & UIEvents::Mod_Shift ? InputEventsTarget::CollapseSelection::No : InputEventsTarget::CollapseSelection::Yes; + if (key == UIEvents::KeyCode::Key_Up) { + target->decrement_cursor_position_to_previous_line(collapse); + } else { + target->increment_cursor_position_to_next_line(collapse); + } + return EventResult::Handled; + } + if (key == UIEvents::KeyCode::Key_Home) { auto collapse = modifiers & UIEvents::Mod_Shift ? InputEventsTarget::CollapseSelection::No : InputEventsTarget::CollapseSelection::Yes; target->move_cursor_to_start(collapse);