diff --git a/Applications/TextEditor/TextEditorWidget.cpp b/Applications/TextEditor/TextEditorWidget.cpp index b0fa3ed791..cdf33c7419 100644 --- a/Applications/TextEditor/TextEditorWidget.cpp +++ b/Applications/TextEditor/TextEditorWidget.cpp @@ -50,7 +50,7 @@ TextEditorWidget::TextEditorWidget() dbg() << "find_next(\"\")"; return; } - auto found_range = m_editor->find_next(needle, m_editor->normalized_selection().end()); + auto found_range = m_editor->document().find_next(needle, m_editor->normalized_selection().end()); dbg() << "find_next(\"" << needle << "\") returned " << found_range; if (found_range.is_valid()) { m_editor->set_selection(found_range); @@ -73,7 +73,7 @@ TextEditorWidget::TextEditorWidget() if (!selection_start.is_valid()) selection_start = m_editor->normalized_selection().end(); - auto found_range = m_editor->find_prev(needle, selection_start); + auto found_range = m_editor->document().find_previous(needle, selection_start); dbg() << "find_prev(\"" << needle << "\") returned " << found_range; if (found_range.is_valid()) { diff --git a/Libraries/LibGUI/GTextDocument.cpp b/Libraries/LibGUI/GTextDocument.cpp index fcaeb33b8e..5eb40a6fc9 100644 --- a/Libraries/LibGUI/GTextDocument.cpp +++ b/Libraries/LibGUI/GTextDocument.cpp @@ -183,3 +183,118 @@ String GTextDocument::text_in_range(const GTextRange& a_range) const return builder.to_string(); } + +char GTextDocument::character_at(const GTextPosition& position) const +{ + ASSERT(position.line() < line_count()); + auto& line = lines()[position.line()]; + if (position.column() == line.length()) + return '\n'; + return line.characters()[position.column()]; +} + +GTextPosition GTextDocument::next_position_after(const GTextPosition& position, SearchShouldWrap should_wrap) const +{ + auto& line = lines()[position.line()]; + if (position.column() == line.length()) { + if (position.line() == line_count() - 1) { + if (should_wrap == SearchShouldWrap::Yes) + return { 0, 0 }; + return {}; + } + return { position.line() + 1, 0 }; + } + return { position.line(), position.column() + 1 }; +} + +GTextPosition GTextDocument::previous_position_before(const GTextPosition& position, SearchShouldWrap should_wrap) const +{ + if (position.column() == 0) { + if (position.line() == 0) { + if (should_wrap == SearchShouldWrap::Yes) { + auto& last_line = lines()[line_count() - 1]; + return { line_count() - 1, last_line.length() }; + } + return {}; + } + auto& prev_line = lines()[position.line() - 1]; + return { position.line() - 1, prev_line.length() }; + } + return { position.line(), position.column() - 1 }; +} + +GTextRange GTextDocument::find_next(const StringView& needle, const GTextPosition& start, SearchShouldWrap should_wrap) const +{ + if (needle.is_empty()) + return {}; + + GTextPosition position = start.is_valid() ? start : GTextPosition(0, 0); + GTextPosition original_position = position; + + GTextPosition start_of_potential_match; + int needle_index = 0; + + do { + auto ch = character_at(position); + if (ch == needle[needle_index]) { + if (needle_index == 0) + start_of_potential_match = position; + ++needle_index; + if (needle_index >= needle.length()) + return { start_of_potential_match, next_position_after(position, should_wrap) }; + } 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 {}; +} + +GTextRange GTextDocument::find_previous(const StringView& needle, const GTextPosition& start, SearchShouldWrap should_wrap) const +{ + if (needle.is_empty()) + return {}; + + GTextPosition position = start.is_valid() ? start : GTextPosition(0, 0); + position = previous_position_before(position, should_wrap); + GTextPosition original_position = position; + + GTextPosition end_of_potential_match; + int needle_index = needle.length() - 1; + + do { + auto ch = character_at(position); + if (ch == needle[needle_index]) { + if (needle_index == needle.length() - 1) + end_of_potential_match = position; + --needle_index; + if (needle_index < 0) + return { position, next_position_after(end_of_potential_match, should_wrap) }; + } 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 {}; +} + +Vector GTextDocument::find_all(const StringView& needle) const +{ + Vector ranges; + + GTextPosition position; + for (;;) { + auto range = find_next(needle, position, SearchShouldWrap::No); + if (!range.is_valid()) + break; + ranges.append(range); + position = range.end(); + } + return ranges; +} diff --git a/Libraries/LibGUI/GTextDocument.h b/Libraries/LibGUI/GTextDocument.h index 3578f3d551..5bf0954f83 100644 --- a/Libraries/LibGUI/GTextDocument.h +++ b/Libraries/LibGUI/GTextDocument.h @@ -20,6 +20,11 @@ struct GTextDocumentSpan { class GTextDocument : public RefCounted { public: + enum class SearchShouldWrap { + No = 0, + Yes + }; + class Client { public: virtual ~Client(); @@ -61,6 +66,16 @@ public: String text_in_range(const GTextRange&) const; + Vector find_all(const StringView& needle) const; + + GTextRange find_next(const StringView&, const GTextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes) const; + GTextRange find_previous(const StringView&, const GTextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes) const; + + GTextPosition next_position_after(const GTextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const; + GTextPosition previous_position_before(const GTextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const; + + char character_at(const GTextPosition&) const; + private: explicit GTextDocument(Client* client); diff --git a/Libraries/LibGUI/GTextEditor.cpp b/Libraries/LibGUI/GTextEditor.cpp index 148c60f584..ff0654a22c 100644 --- a/Libraries/LibGUI/GTextEditor.cpp +++ b/Libraries/LibGUI/GTextEditor.cpp @@ -1176,97 +1176,6 @@ void GTextEditor::resize_event(GResizeEvent& event) recompute_all_visual_lines(); } -GTextPosition GTextEditor::next_position_after(const GTextPosition& position, ShouldWrapAtEndOfDocument should_wrap) -{ - auto& line = lines()[position.line()]; - if (position.column() == line.length()) { - if (position.line() == line_count() - 1) { - if (should_wrap == ShouldWrapAtEndOfDocument::Yes) - return { 0, 0 }; - return {}; - } - return { position.line() + 1, 0 }; - } - return { position.line(), position.column() + 1 }; -} - -GTextPosition GTextEditor::prev_position_before(const GTextPosition& position, ShouldWrapAtStartOfDocument should_wrap) -{ - if (position.column() == 0) { - if (position.line() == 0) { - if (should_wrap == ShouldWrapAtStartOfDocument::Yes) { - auto& last_line = lines()[line_count() - 1]; - return { line_count() - 1, last_line.length() }; - } - return {}; - } - auto& prev_line = lines()[position.line() - 1]; - return { position.line() - 1, prev_line.length() }; - } - return { position.line(), position.column() - 1 }; -} - -GTextRange GTextEditor::find_next(const StringView& needle, const GTextPosition& start) -{ - if (needle.is_empty()) - return {}; - - GTextPosition position = start.is_valid() ? start : GTextPosition(0, 0); - GTextPosition original_position = position; - - GTextPosition start_of_potential_match; - int needle_index = 0; - - do { - auto ch = character_at(position); - if (ch == needle[needle_index]) { - if (needle_index == 0) - start_of_potential_match = position; - ++needle_index; - if (needle_index >= needle.length()) - return { start_of_potential_match, next_position_after(position) }; - } else { - if (needle_index > 0) - position = start_of_potential_match; - needle_index = 0; - } - position = next_position_after(position); - } while (position.is_valid() && position != original_position); - - return {}; -} - -GTextRange GTextEditor::find_prev(const StringView& needle, const GTextPosition& start) -{ - if (needle.is_empty()) - return {}; - - GTextPosition position = start.is_valid() ? start : GTextPosition(0, 0); - position = prev_position_before(position); - GTextPosition original_position = position; - - GTextPosition end_of_potential_match; - int needle_index = needle.length() - 1; - - do { - auto ch = character_at(position); - if (ch == needle[needle_index]) { - if (needle_index == needle.length() - 1) - end_of_potential_match = position; - --needle_index; - if (needle_index < 0) - return { position, next_position_after(end_of_potential_match) }; - } else { - if (needle_index < needle.length() - 1) - position = end_of_potential_match; - needle_index = needle.length() - 1; - } - position = prev_position_before(position); - } while (position.is_valid() && position != original_position); - - return {}; -} - void GTextEditor::set_selection(const GTextRange& selection) { if (m_selection == selection) @@ -1277,15 +1186,6 @@ void GTextEditor::set_selection(const GTextRange& selection) update(); } -char GTextEditor::character_at(const GTextPosition& position) const -{ - ASSERT(position.line() < line_count()); - auto& line = lines()[position.line()]; - if (position.column() == line.length()) - return '\n'; - return line.characters()[position.column()]; -} - void GTextEditor::recompute_all_visual_lines() { int y_offset = 0; diff --git a/Libraries/LibGUI/GTextEditor.h b/Libraries/LibGUI/GTextEditor.h index 20e684e04a..89f9ff5040 100644 --- a/Libraries/LibGUI/GTextEditor.h +++ b/Libraries/LibGUI/GTextEditor.h @@ -14,15 +14,6 @@ class GMenu; class GScrollBar; class Painter; -enum class ShouldWrapAtEndOfDocument { - No = 0, - Yes -}; -enum class ShouldWrapAtStartOfDocument { - No = 0, - Yes -}; - class GTextEditor : public GScrollableWidget , public GTextDocument::Client { @@ -74,12 +65,6 @@ public: bool write_to_file(const StringView& path); - GTextRange find_next(const StringView&, const GTextPosition& start = {}); - GTextRange find_prev(const StringView&, const GTextPosition& start = {}); - - GTextPosition next_position_after(const GTextPosition&, ShouldWrapAtEndOfDocument = ShouldWrapAtEndOfDocument::Yes); - GTextPosition prev_position_before(const GTextPosition&, ShouldWrapAtStartOfDocument = ShouldWrapAtStartOfDocument::Yes); - bool has_selection() const { return m_selection.is_valid(); } String selected_text() const; void set_selection(const GTextRange&); @@ -167,7 +152,6 @@ private: void delete_selection(); void did_update_selection(); int content_x_for_position(const GTextPosition&) const; - char character_at(const GTextPosition&) const; Rect ruler_rect_in_inner_coordinates() const; Rect visible_text_rect_in_inner_coordinates() const; void recompute_all_visual_lines();