diff --git a/Libraries/LibVT/TerminalWidget.cpp b/Libraries/LibVT/TerminalWidget.cpp index 9270facd5a..59a223d222 100644 --- a/Libraries/LibVT/TerminalWidget.cpp +++ b/Libraries/LibVT/TerminalWidget.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -540,6 +541,119 @@ VT::Position TerminalWidget::buffer_position_at(const Gfx::IntPoint& position) c return { row, column }; } +u32 TerminalWidget::code_point_at(const VT::Position& position) const +{ + ASSERT(position.row() >= 0 && static_cast(position.row()) < m_terminal.line_count()); + auto& line = m_terminal.line(position.row()); + if (position.column() == line.length()) + return '\n'; + return line.code_point(position.column()); +} + +VT::Position TerminalWidget::next_position_after(const VT::Position& position, bool should_wrap) const +{ + ASSERT(position.row() >= 0 && static_cast(position.row()) < m_terminal.line_count()); + auto& line = m_terminal.line(position.row()); + if (position.column() == line.length()) { + if (static_cast(position.row()) == m_terminal.line_count() - 1) { + if (should_wrap) + return { 0, 0 }; + return {}; + } + return { position.row() + 1, 0 }; + } + return { position.row(), position.column() + 1 }; +} + +VT::Position TerminalWidget::previous_position_before(const VT::Position& position, bool should_wrap) const +{ + ASSERT(position.row() >= 0 && static_cast(position.row()) < m_terminal.line_count()); + if (position.column() == 0) { + if (position.row() == 0) { + if (should_wrap) { + auto& last_line = m_terminal.line(m_terminal.line_count() - 1); + return { static_cast(m_terminal.line_count() - 1), last_line.length() }; + } + return {}; + } + auto& prev_line = m_terminal.line(position.row() - 1); + return { position.row() - 1, prev_line.length() }; + } + return { position.row(), position.column() - 1 }; +} + +static u32 to_lowercase_code_point(u32 code_point) +{ + // FIXME: this only handles ascii characters, but handling unicode lowercasing seems like a mess + if (code_point < 128) + return tolower(code_point); + return code_point; +} + +VT::Range TerminalWidget::find_next(const StringView& needle, const VT::Position& start, bool case_sensitivity, bool should_wrap) +{ + if (needle.is_empty()) + return {}; + + VT::Position position = start.is_valid() ? start : VT::Position(0, 0); + VT::Position original_position = position; + + VT::Position start_of_potential_match; + size_t needle_index = 0; + + do { + auto ch = code_point_at(position); + // FIXME: This is not the right way to use a Unicode needle! + auto needle_ch = (u32)needle[needle_index]; + if (case_sensitivity ? ch == needle_ch : to_lowercase_code_point(ch) == to_lowercase_code_point(needle_ch)) { + if (needle_index == 0) + start_of_potential_match = position; + ++needle_index; + if (needle_index >= needle.length()) + return { start_of_potential_match, position }; + } else { + if (needle_index > 0) + position = start_of_potential_match; + needle_index = 0; + } + position = next_position_after(position, should_wrap); + } while (position.is_valid() && position != original_position); + + return {}; +} + +VT::Range TerminalWidget::find_previous(const StringView& needle, const VT::Position& start, bool case_sensitivity, bool should_wrap) +{ + if (needle.is_empty()) + return {}; + + VT::Position position = start.is_valid() ? start : VT::Position(m_terminal.line_count() - 1, m_terminal.line(m_terminal.line_count() - 1).length() - 1); + VT::Position original_position = position; + + VT::Position end_of_potential_match; + size_t needle_index = needle.length() - 1; + + do { + auto ch = code_point_at(position); + // FIXME: This is not the right way to use a Unicode needle! + auto needle_ch = (u32)needle[needle_index]; + if (case_sensitivity ? ch == needle_ch : to_lowercase_code_point(ch) == to_lowercase_code_point(needle_ch)) { + if (needle_index == needle.length() - 1) + end_of_potential_match = position; + if (needle_index == 0) + return { position, end_of_potential_match }; + --needle_index; + } else { + if (needle_index < needle.length() - 1) + position = end_of_potential_match; + needle_index = needle.length() - 1; + } + position = previous_position_before(position, should_wrap); + } while (position.is_valid() && position != original_position); + + return {}; +} + void TerminalWidget::doubleclick_event(GUI::MouseEvent& event) { if (event.button() == GUI::MouseButton::Left) { @@ -936,6 +1050,11 @@ void TerminalWidget::scroll_to_bottom() m_scrollbar->set_value(m_scrollbar->max()); } +void TerminalWidget::scroll_to_row(int row) +{ + m_scrollbar->set_value(row); +} + void TerminalWidget::update_copy_action() { m_copy_action->set_enabled(has_selection()); diff --git a/Libraries/LibVT/TerminalWidget.h b/Libraries/LibVT/TerminalWidget.h index 63b57393d5..195e3c4a3f 100644 --- a/Libraries/LibVT/TerminalWidget.h +++ b/Libraries/LibVT/TerminalWidget.h @@ -81,7 +81,11 @@ public: void set_selection(const VT::Range& selection); VT::Position buffer_position_at(const Gfx::IntPoint&) const; + VT::Range find_next(const StringView&, const VT::Position& start = {}, bool case_sensitivity = false, bool should_wrap = false); + VT::Range find_previous(const StringView&, const VT::Position& start = {}, bool case_sensitivity = false, bool should_wrap = false); + void scroll_to_bottom(); + void scroll_to_row(int); bool is_scrollable() const; int scroll_length() const; @@ -145,6 +149,10 @@ private: int first_selection_column_on_row(int row) const; int last_selection_column_on_row(int row) const; + u32 code_point_at(const VT::Position&) const; + VT::Position next_position_after(const VT::Position&, bool should_wrap) const; + VT::Position previous_position_before(const VT::Position&, bool should_wrap) const; + VT::Terminal m_terminal; VT::Range m_selection;