mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 13:28:11 +00:00
LibGUI: Move text search functions from GTextEditor to GTextDocument
Also add a find_all() that retuns a Vector<GTextRange> and simply does a find_next() loop, returning all the matching ranges.
This commit is contained in:
parent
b81f6f2c43
commit
b8bf998b61
5 changed files with 132 additions and 118 deletions
|
@ -50,7 +50,7 @@ TextEditorWidget::TextEditorWidget()
|
||||||
dbg() << "find_next(\"\")";
|
dbg() << "find_next(\"\")";
|
||||||
return;
|
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;
|
dbg() << "find_next(\"" << needle << "\") returned " << found_range;
|
||||||
if (found_range.is_valid()) {
|
if (found_range.is_valid()) {
|
||||||
m_editor->set_selection(found_range);
|
m_editor->set_selection(found_range);
|
||||||
|
@ -73,7 +73,7 @@ TextEditorWidget::TextEditorWidget()
|
||||||
if (!selection_start.is_valid())
|
if (!selection_start.is_valid())
|
||||||
selection_start = m_editor->normalized_selection().end();
|
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;
|
dbg() << "find_prev(\"" << needle << "\") returned " << found_range;
|
||||||
if (found_range.is_valid()) {
|
if (found_range.is_valid()) {
|
||||||
|
|
|
@ -183,3 +183,118 @@ String GTextDocument::text_in_range(const GTextRange& a_range) const
|
||||||
|
|
||||||
return builder.to_string();
|
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<GTextRange> GTextDocument::find_all(const StringView& needle) const
|
||||||
|
{
|
||||||
|
Vector<GTextRange> 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;
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ struct GTextDocumentSpan {
|
||||||
|
|
||||||
class GTextDocument : public RefCounted<GTextDocument> {
|
class GTextDocument : public RefCounted<GTextDocument> {
|
||||||
public:
|
public:
|
||||||
|
enum class SearchShouldWrap {
|
||||||
|
No = 0,
|
||||||
|
Yes
|
||||||
|
};
|
||||||
|
|
||||||
class Client {
|
class Client {
|
||||||
public:
|
public:
|
||||||
virtual ~Client();
|
virtual ~Client();
|
||||||
|
@ -61,6 +66,16 @@ public:
|
||||||
|
|
||||||
String text_in_range(const GTextRange&) const;
|
String text_in_range(const GTextRange&) const;
|
||||||
|
|
||||||
|
Vector<GTextRange> 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:
|
private:
|
||||||
explicit GTextDocument(Client* client);
|
explicit GTextDocument(Client* client);
|
||||||
|
|
||||||
|
|
|
@ -1176,97 +1176,6 @@ void GTextEditor::resize_event(GResizeEvent& event)
|
||||||
recompute_all_visual_lines();
|
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)
|
void GTextEditor::set_selection(const GTextRange& selection)
|
||||||
{
|
{
|
||||||
if (m_selection == selection)
|
if (m_selection == selection)
|
||||||
|
@ -1277,15 +1186,6 @@ void GTextEditor::set_selection(const GTextRange& selection)
|
||||||
update();
|
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()
|
void GTextEditor::recompute_all_visual_lines()
|
||||||
{
|
{
|
||||||
int y_offset = 0;
|
int y_offset = 0;
|
||||||
|
|
|
@ -14,15 +14,6 @@ class GMenu;
|
||||||
class GScrollBar;
|
class GScrollBar;
|
||||||
class Painter;
|
class Painter;
|
||||||
|
|
||||||
enum class ShouldWrapAtEndOfDocument {
|
|
||||||
No = 0,
|
|
||||||
Yes
|
|
||||||
};
|
|
||||||
enum class ShouldWrapAtStartOfDocument {
|
|
||||||
No = 0,
|
|
||||||
Yes
|
|
||||||
};
|
|
||||||
|
|
||||||
class GTextEditor
|
class GTextEditor
|
||||||
: public GScrollableWidget
|
: public GScrollableWidget
|
||||||
, public GTextDocument::Client {
|
, public GTextDocument::Client {
|
||||||
|
@ -74,12 +65,6 @@ public:
|
||||||
|
|
||||||
bool write_to_file(const StringView& path);
|
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(); }
|
bool has_selection() const { return m_selection.is_valid(); }
|
||||||
String selected_text() const;
|
String selected_text() const;
|
||||||
void set_selection(const GTextRange&);
|
void set_selection(const GTextRange&);
|
||||||
|
@ -167,7 +152,6 @@ private:
|
||||||
void delete_selection();
|
void delete_selection();
|
||||||
void did_update_selection();
|
void did_update_selection();
|
||||||
int content_x_for_position(const GTextPosition&) const;
|
int content_x_for_position(const GTextPosition&) const;
|
||||||
char character_at(const GTextPosition&) const;
|
|
||||||
Rect ruler_rect_in_inner_coordinates() const;
|
Rect ruler_rect_in_inner_coordinates() const;
|
||||||
Rect visible_text_rect_in_inner_coordinates() const;
|
Rect visible_text_rect_in_inner_coordinates() const;
|
||||||
void recompute_all_visual_lines();
|
void recompute_all_visual_lines();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue