From 5f2a0f03a6458a61e936f2aff7551886931c2237 Mon Sep 17 00:00:00 2001 From: Itamar Date: Tue, 29 Mar 2022 16:33:46 +0300 Subject: [PATCH] LibGUI: Add search API to TextEditor with highlighted results This adds a search API to TextEditor. The API that is similar to "find_text" of TextDocument (which is used internally to do the search). All search results (as well as the current one) are highlighted with a "span collection", which is pretty neat :^) --- Userland/Libraries/LibGUI/TextEditor.cpp | 59 ++++++++++++++++++++++++ Userland/Libraries/LibGUI/TextEditor.h | 17 ++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibGUI/TextEditor.cpp b/Userland/Libraries/LibGUI/TextEditor.cpp index 36cbca5f88..45d8568cb3 100644 --- a/Userland/Libraries/LibGUI/TextEditor.cpp +++ b/Userland/Libraries/LibGUI/TextEditor.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2021, Jakob-Niklas See + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -2105,4 +2106,62 @@ void TextEditor::set_text_is_secret(bool text_is_secret) did_update_selection(); } +TextRange TextEditor::find_text(StringView needle, SearchDirection direction, GUI::TextDocument::SearchShouldWrap should_wrap, bool use_regex, bool match_case) +{ + GUI::TextRange range {}; + if (direction == SearchDirection::Forward) { + range = document().find_next(needle, + m_search_result_index.has_value() ? m_search_results[*m_search_result_index].end() : GUI::TextPosition {}, + should_wrap, use_regex, match_case); + } else { + range = document().find_previous(needle, + m_search_result_index.has_value() ? m_search_results[*m_search_result_index].start() : GUI::TextPosition {}, + should_wrap, use_regex, match_case); + } + + if (!range.is_valid()) { + reset_search_results(); + return {}; + } + + auto all_results = document().find_all(needle, use_regex, match_case); + on_search_results(range, all_results); + return range; +} + +void TextEditor::reset_search_results() +{ + m_search_result_index.clear(); + m_search_results.clear(); + document().set_spans(search_results_span_collection_index, {}); + update(); +} + +void TextEditor::on_search_results(GUI::TextRange current, Vector all_results) +{ + m_search_result_index.clear(); + m_search_results.clear(); + + set_cursor(current.start()); + if (auto it = all_results.find(current); it->is_valid()) + m_search_result_index = it.index(); + m_search_results = move(all_results); + + Vector spans; + for (size_t i = 0; i < m_search_results.size(); ++i) { + auto& result = m_search_results[i]; + GUI::TextDocumentSpan span; + span.range = result; + span.attributes.background_color = palette().hover_highlight(); + span.attributes.color = Color::from_argb(0xff000000); // So text without spans from a highlighter will have color + if (i == m_search_result_index) { + span.attributes.bold = true; + span.attributes.underline = true; + } + spans.append(move(span)); + } + document().set_spans(search_results_span_collection_index, move(spans)); + update(); +} + } diff --git a/Userland/Libraries/LibGUI/TextEditor.h b/Userland/Libraries/LibGUI/TextEditor.h index e3d2fa454e..ac3d536126 100644 --- a/Userland/Libraries/LibGUI/TextEditor.h +++ b/Userland/Libraries/LibGUI/TextEditor.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -213,6 +214,15 @@ public: void set_text_is_secret(bool text_is_secret); void force_rehighlight(); + enum class SearchDirection { + Forward, + Backward, + }; + TextRange find_text(StringView needle, SearchDirection, GUI::TextDocument::SearchShouldWrap, bool use_regex, bool match_case); + void reset_search_results(); + Optional search_result_index() const { return m_search_result_index; } + Vector const& search_results() const { return m_search_results; } + protected: explicit TextEditor(Type = Type::MultiLine); @@ -260,7 +270,6 @@ private: // ^Syntax::HighlighterClient virtual Vector& spans() final { return document().spans(); } virtual Vector const& spans() const final { return document().spans(); } - virtual void highlighter_did_set_spans(Vector spans) final { document().set_spans(move(spans)); } virtual void set_span_at_index(size_t index, TextDocumentSpan span) final { document().set_span_at_index(index, move(span)); } virtual void highlighter_did_request_update() final { update(); } virtual String highlighter_did_request_text() const final { return text(); } @@ -337,6 +346,9 @@ private: } virtual void will_execute(TextDocumentUndoCommand const&) { } + void on_search_results(GUI::TextRange current, Vector all_results); + + static constexpr auto search_results_span_collection_index = 1; Type m_type { MultiLine }; Mode m_mode { Editable }; @@ -408,6 +420,9 @@ private: RefPtr m_icon; bool m_text_is_secret { false }; + + Optional m_search_result_index; + Vector m_search_results; }; }