summaryrefslogtreecommitdiff
path: root/Libraries
diff options
context:
space:
mode:
Diffstat (limited to 'Libraries')
-rw-r--r--Libraries/LibGUI/GTextEditor.cpp100
-rw-r--r--Libraries/LibGUI/GTextEditor.h30
2 files changed, 116 insertions, 14 deletions
diff --git a/Libraries/LibGUI/GTextEditor.cpp b/Libraries/LibGUI/GTextEditor.cpp
index e43590a186..841cef6493 100644
--- a/Libraries/LibGUI/GTextEditor.cpp
+++ b/Libraries/LibGUI/GTextEditor.cpp
@@ -668,21 +668,27 @@ int GTextEditor::content_x_for_position(const GTextPosition& position) const
}
}
-Rect GTextEditor::cursor_content_rect() const
+Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
{
- if (!m_cursor.is_valid())
+ if (!position.is_valid())
return {};
ASSERT(!m_lines.is_empty());
- ASSERT(m_cursor.column() <= (current_line().length() + 1));
+ ASSERT(position.column() <= (current_line().length() + 1));
- int cursor_x = content_x_for_position(m_cursor);
+ int x = content_x_for_position(position);
if (is_single_line()) {
- Rect cursor_rect { cursor_x, 0, 1, font().glyph_height() + 2 };
- cursor_rect.center_vertically_within({ {}, frame_inner_rect().size() });
- return cursor_rect;
+ Rect rect { x, 0, 1, font().glyph_height() + 2 };
+ rect.center_vertically_within({ {}, frame_inner_rect().size() });
+ return rect;
}
- return { cursor_x, m_cursor.line() * line_height(), 1, line_height() };
+ return { x, position.line() * line_height(), 1, line_height() };
+}
+
+
+Rect GTextEditor::cursor_content_rect() const
+{
+ return content_rect_for_position(m_cursor);
}
Rect GTextEditor::line_widget_rect(int line_index) const
@@ -696,16 +702,21 @@ Rect GTextEditor::line_widget_rect(int line_index) const
return rect;
}
-void GTextEditor::scroll_cursor_into_view()
+void GTextEditor::scroll_position_into_view(const GTextPosition& position)
{
- auto rect = cursor_content_rect();
- if (m_cursor.column() == 0)
- rect.set_x(content_x_for_position({ m_cursor.line(), 0 }) - 2);
- else if (m_cursor.column() == m_lines[m_cursor.line()].length())
- rect.set_x(content_x_for_position({ m_cursor.line(), m_lines[m_cursor.line()].length() }) + 2);
+ auto rect = content_rect_for_position(position);
+ if (position.column() == 0)
+ rect.set_x(content_x_for_position({ position.line(), 0 }) - 2);
+ else if (position.column() == m_lines[position.line()].length())
+ rect.set_x(content_x_for_position({ position.line(), m_lines[position.line()].length() }) + 2);
scroll_into_view(rect, true, true);
}
+void GTextEditor::scroll_cursor_into_view()
+{
+ scroll_position_into_view(m_cursor);
+}
+
Rect GTextEditor::line_content_rect(int line_index) const
{
auto& line = m_lines[line_index];
@@ -1079,3 +1090,64 @@ void GTextEditor::resize_event(GResizeEvent& event)
GScrollableWidget::resize_event(event);
update_content_size();
}
+
+GTextPosition GTextEditor::next_position_after(const GTextPosition& position, ShouldWrapAtEndOfDocument should_wrap)
+{
+ auto& line = m_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 };
+}
+
+GTextRange GTextEditor::find(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 {
+ needle_index = 0;
+ }
+ position = next_position_after(position);
+ } while(position.is_valid() && position != original_position);
+
+ return {};
+}
+
+void GTextEditor::set_selection(const GTextRange& selection)
+{
+ if (m_selection == selection)
+ return;
+ m_selection = selection;
+ set_cursor(m_selection.end());
+ scroll_position_into_view(normalized_selection().start());
+ update();
+}
+
+char GTextEditor::character_at(const GTextPosition& position) const
+{
+ ASSERT(position.line() < line_count());
+ auto& line = m_lines[position.line()];
+ if (position.column() == line.length())
+ return '\n';
+ return line.characters()[position.column()];
+}
diff --git a/Libraries/LibGUI/GTextEditor.h b/Libraries/LibGUI/GTextEditor.h
index 2b858e8d05..be9468644b 100644
--- a/Libraries/LibGUI/GTextEditor.h
+++ b/Libraries/LibGUI/GTextEditor.h
@@ -11,6 +11,8 @@ class GMenu;
class GScrollBar;
class Painter;
+enum class ShouldWrapAtEndOfDocument { No = 0, Yes };
+
class GTextPosition {
public:
GTextPosition() {}
@@ -69,6 +71,11 @@ public:
m_end = end;
}
+ bool operator==(const GTextRange& other) const
+ {
+ return m_start == other.m_start && m_end == other.m_end;
+ }
+
private:
GTextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; }
GTextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; }
@@ -108,6 +115,7 @@ public:
void set_text(const StringView&);
void scroll_cursor_into_view();
+ void scroll_position_into_view(const GTextPosition&);
int line_count() const { return m_lines.size(); }
int line_spacing() const { return m_line_spacing; }
int line_height() const { return font().glyph_height() + m_line_spacing; }
@@ -118,8 +126,13 @@ public:
bool write_to_file(const StringView& path);
+ GTextRange find(const StringView&, const GTextPosition& start = {});
+ GTextPosition next_position_after(const GTextPosition&, ShouldWrapAtEndOfDocument = ShouldWrapAtEndOfDocument::Yes);
+
bool has_selection() const { return m_selection.is_valid(); }
String selected_text() const;
+ void set_selection(const GTextRange&);
+
String text() const;
void clear();
@@ -192,6 +205,7 @@ private:
Rect line_content_rect(int item_index) const;
Rect line_widget_rect(int line_index) const;
Rect cursor_content_rect() const;
+ Rect content_rect_for_position(const GTextPosition&) const;
void update_cursor();
void set_cursor(int line, int column);
void set_cursor(const GTextPosition&);
@@ -207,6 +221,7 @@ private:
void delete_selection();
void did_update_selection();
int content_x_for_position(const GTextPosition&) const;
+ char character_at(const GTextPosition&) const;
Type m_type { MultiLine };
@@ -232,3 +247,18 @@ private:
RefPtr<GAction> m_delete_action;
CElapsedTimer m_triple_click_timer;
};
+
+inline const LogStream& operator<<(const LogStream& stream, const GTextPosition& value)
+{
+ if (!value.is_valid())
+ return stream << "GTextPosition(Invalid)";
+ return stream << String::format("(%d,%d)", value.line(), value.column());
+}
+
+inline const LogStream& operator<<(const LogStream& stream, const GTextRange& value)
+{
+ if (!value.is_valid())
+ return stream << "GTextRange(Invalid)";
+ return stream << value.start() << '-' << value.end();
+}
+