From 0ddde46c193120c9b5b7f1dcdbea5149ed89e6e5 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Mon, 28 Dec 2020 11:23:38 +0200 Subject: [PATCH] LibVT: Implement find and scroll helper methods in TerminalWidget This is mostly based on TextDocument's similar methods, these will help implement searching within the terminal application. The support for unicode code points is shaky at best, and should probably be improved further. --- Libraries/LibVT/TerminalWidget.cpp | 119 +++++++++++++++++++++++++++++ Libraries/LibVT/TerminalWidget.h | 8 ++ 2 files changed, 127 insertions(+) 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;