mirror of
https://github.com/RGBCube/serenity
synced 2025-07-02 12:42:13 +00:00
LibGUI: Move GTextDocument out of GTextEditor
The idea here is to decouple the document from the editor widget so you could have multiple editors being views onto the same document. This doesn't work yet, since the document and editor are coupled in various ways still (including a per-line back-pointer to the editor.)
This commit is contained in:
parent
1bcbc3f827
commit
f1c6193d6d
6 changed files with 302 additions and 240 deletions
|
@ -19,6 +19,7 @@ GTextEditor::GTextEditor(Type type, GWidget* parent)
|
|||
: GScrollableWidget(parent)
|
||||
, m_type(type)
|
||||
{
|
||||
m_document = GTextDocument::create(*this);
|
||||
set_frame_shape(FrameShape::Container);
|
||||
set_frame_shadow(FrameShadow::Sunken);
|
||||
set_frame_thickness(2);
|
||||
|
@ -26,7 +27,6 @@ GTextEditor::GTextEditor(Type type, GWidget* parent)
|
|||
set_font(GFontDatabase::the().get_by_name("Csilla Thin"));
|
||||
// FIXME: Recompute vertical scrollbar step size on font change.
|
||||
vertical_scrollbar().set_step(line_height());
|
||||
m_lines.append(make<Line>(*this));
|
||||
m_cursor = { 0, 0 };
|
||||
create_actions();
|
||||
}
|
||||
|
@ -51,33 +51,17 @@ void GTextEditor::create_actions()
|
|||
|
||||
void GTextEditor::set_text(const StringView& text)
|
||||
{
|
||||
if (is_single_line() && text.length() == m_lines[0].length() && !memcmp(text.characters_without_null_termination(), m_lines[0].characters(), text.length()))
|
||||
if (is_single_line() && text.length() == line(0).length() && !memcmp(text.characters_without_null_termination(), line(0).characters(), text.length()))
|
||||
return;
|
||||
|
||||
m_spans.clear();
|
||||
|
||||
m_selection.clear();
|
||||
m_lines.clear();
|
||||
int start_of_current_line = 0;
|
||||
|
||||
auto add_line = [&](int current_position) {
|
||||
int line_length = current_position - start_of_current_line;
|
||||
auto line = make<Line>(*this);
|
||||
if (line_length)
|
||||
line->set_text(text.substring_view(start_of_current_line, current_position - start_of_current_line));
|
||||
m_lines.append(move(line));
|
||||
start_of_current_line = current_position + 1;
|
||||
};
|
||||
int i = 0;
|
||||
for (i = 0; i < text.length(); ++i) {
|
||||
if (text[i] == '\n')
|
||||
add_line(i);
|
||||
}
|
||||
add_line(i);
|
||||
document().set_text(text);
|
||||
|
||||
update_content_size();
|
||||
recompute_all_visual_lines();
|
||||
if (is_single_line())
|
||||
set_cursor(0, m_lines[0].length());
|
||||
set_cursor(0, line(0).length());
|
||||
else
|
||||
set_cursor(0, 0);
|
||||
did_update_selection();
|
||||
|
@ -88,7 +72,7 @@ void GTextEditor::update_content_size()
|
|||
{
|
||||
int content_width = 0;
|
||||
int content_height = 0;
|
||||
for (auto& line : m_lines) {
|
||||
for (auto& line : document().lines()) {
|
||||
content_width = max(line.m_visual_rect.width(), content_width);
|
||||
content_height += line.m_visual_rect.height();
|
||||
}
|
||||
|
@ -110,13 +94,13 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
|
|||
int line_index = -1;
|
||||
|
||||
if (is_line_wrapping_enabled()) {
|
||||
for (int i = 0; i < m_lines.size(); ++i) {
|
||||
auto& rect = m_lines[i].m_visual_rect;
|
||||
for (int i = 0; i < lines().size(); ++i) {
|
||||
auto& rect = lines()[i].m_visual_rect;
|
||||
if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
|
||||
line_index = i;
|
||||
break;
|
||||
} else if (position.y() > rect.bottom())
|
||||
line_index = m_lines.size() - 1;
|
||||
line_index = lines().size() - 1;
|
||||
}
|
||||
} else {
|
||||
line_index = position.y() / line_height();
|
||||
|
@ -124,7 +108,7 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
|
|||
|
||||
line_index = max(0, min(line_index, line_count() - 1));
|
||||
|
||||
auto& line = m_lines[line_index];
|
||||
auto& line = lines()[line_index];
|
||||
|
||||
int column_index;
|
||||
switch (m_text_alignment) {
|
||||
|
@ -149,7 +133,7 @@ GTextPosition GTextEditor::text_position_at(const Point& a_position) const
|
|||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
column_index = max(0, min(column_index, m_lines[line_index].length()));
|
||||
column_index = max(0, min(column_index, lines()[line_index].length()));
|
||||
return { line_index, column_index };
|
||||
}
|
||||
|
||||
|
@ -163,9 +147,9 @@ void GTextEditor::doubleclick_event(GMouseEvent& event)
|
|||
|
||||
auto start = text_position_at(event.position());
|
||||
auto end = start;
|
||||
auto& line = m_lines[start.line()];
|
||||
auto& line = lines()[start.line()];
|
||||
|
||||
if (m_spans.is_empty()) {
|
||||
if (!document().has_spans()) {
|
||||
while (start.column() > 0) {
|
||||
if (isspace(line.characters()[start.column() - 1]))
|
||||
break;
|
||||
|
@ -178,7 +162,7 @@ void GTextEditor::doubleclick_event(GMouseEvent& event)
|
|||
end.set_column(end.column() + 1);
|
||||
}
|
||||
} else {
|
||||
for (auto& span : m_spans) {
|
||||
for (auto& span : document().spans()) {
|
||||
if (!span.range.contains(start))
|
||||
continue;
|
||||
start = span.range.start();
|
||||
|
@ -209,11 +193,11 @@ void GTextEditor::mousedown_event(GMouseEvent& event)
|
|||
if (is_multi_line()) {
|
||||
// select *current* line
|
||||
start = GTextPosition(m_cursor.line(), 0);
|
||||
end = GTextPosition(m_cursor.line(), m_lines[m_cursor.line()].length());
|
||||
end = GTextPosition(m_cursor.line(), lines()[m_cursor.line()].length());
|
||||
} else {
|
||||
// select *whole* line
|
||||
start = GTextPosition(0, 0);
|
||||
end = GTextPosition(line_count() - 1, m_lines[line_count() - 1].length());
|
||||
end = GTextPosition(line_count() - 1, lines()[line_count() - 1].length());
|
||||
}
|
||||
|
||||
m_selection.set(start, end);
|
||||
|
@ -351,7 +335,7 @@ void GTextEditor::paint_event(GPaintEvent& event)
|
|||
painter.add_clip_rect(text_clip_rect);
|
||||
|
||||
for (int line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
|
||||
auto& line = m_lines[line_index];
|
||||
auto& line = lines()[line_index];
|
||||
|
||||
bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
|
||||
int first_visual_line_with_selection = -1;
|
||||
|
@ -378,7 +362,7 @@ void GTextEditor::paint_event(GPaintEvent& event)
|
|||
#ifdef DEBUG_GTEXTEDITOR
|
||||
painter.draw_rect(visual_line_rect, Color::Cyan);
|
||||
#endif
|
||||
if (m_spans.is_empty()) {
|
||||
if (!document().has_spans()) {
|
||||
// Fast-path for plain text
|
||||
painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, Color::Black);
|
||||
} else {
|
||||
|
@ -389,7 +373,7 @@ void GTextEditor::paint_event(GPaintEvent& event)
|
|||
Color color;
|
||||
GTextPosition physical_position(line_index, start_of_visual_line + i);
|
||||
// FIXME: This is *horribly* inefficient.
|
||||
for (auto& span : m_spans) {
|
||||
for (auto& span : document().spans()) {
|
||||
if (!span.range.contains(physical_position))
|
||||
continue;
|
||||
color = span.color;
|
||||
|
@ -466,7 +450,7 @@ void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event)
|
|||
void GTextEditor::select_all()
|
||||
{
|
||||
GTextPosition start_of_document { 0, 0 };
|
||||
GTextPosition end_of_document { line_count() - 1, m_lines[line_count() - 1].length() };
|
||||
GTextPosition end_of_document { line_count() - 1, lines()[line_count() - 1].length() };
|
||||
m_selection.set(start_of_document, end_of_document);
|
||||
did_update_selection();
|
||||
set_cursor(end_of_document);
|
||||
|
@ -492,7 +476,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
if (event.key() == KeyCode::Key_Up) {
|
||||
if (m_cursor.line() > 0) {
|
||||
int new_line = m_cursor.line() - 1;
|
||||
int new_column = min(m_cursor.column(), m_lines[new_line].length());
|
||||
int new_column = min(m_cursor.column(), lines()[new_line].length());
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(new_line, new_column);
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
|
@ -503,9 +487,9 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
return;
|
||||
}
|
||||
if (event.key() == KeyCode::Key_Down) {
|
||||
if (m_cursor.line() < (m_lines.size() - 1)) {
|
||||
if (m_cursor.line() < (lines().size() - 1)) {
|
||||
int new_line = m_cursor.line() + 1;
|
||||
int new_column = min(m_cursor.column(), m_lines[new_line].length());
|
||||
int new_column = min(m_cursor.column(), lines()[new_line].length());
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(new_line, new_column);
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
|
@ -518,7 +502,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
if (event.key() == KeyCode::Key_PageUp) {
|
||||
if (m_cursor.line() > 0) {
|
||||
int new_line = max(0, m_cursor.line() - visible_content_rect().height() / line_height());
|
||||
int new_column = min(m_cursor.column(), m_lines[new_line].length());
|
||||
int new_column = min(m_cursor.column(), lines()[new_line].length());
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(new_line, new_column);
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
|
@ -529,9 +513,9 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
return;
|
||||
}
|
||||
if (event.key() == KeyCode::Key_PageDown) {
|
||||
if (m_cursor.line() < (m_lines.size() - 1)) {
|
||||
if (m_cursor.line() < (lines().size() - 1)) {
|
||||
int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height());
|
||||
int new_column = min(m_cursor.column(), m_lines[new_line].length());
|
||||
int new_column = min(m_cursor.column(), lines()[new_line].length());
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(new_line, new_column);
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
|
@ -552,7 +536,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
}
|
||||
} else if (m_cursor.line() > 0) {
|
||||
int new_line = m_cursor.line() - 1;
|
||||
int new_column = m_lines[new_line].length();
|
||||
int new_column = lines()[new_line].length();
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(new_line, new_column);
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
|
@ -613,7 +597,7 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
}
|
||||
if (event.ctrl() && event.key() == KeyCode::Key_End) {
|
||||
toggle_selection_if_needed_for_event(event);
|
||||
set_cursor(line_count() - 1, m_lines[line_count() - 1].length());
|
||||
set_cursor(line_count() - 1, lines()[line_count() - 1].length());
|
||||
if (event.shift() && m_selection.start().is_valid()) {
|
||||
m_selection.set_end(m_cursor);
|
||||
did_update_selection();
|
||||
|
@ -655,10 +639,10 @@ void GTextEditor::keydown_event(GKeyEvent& event)
|
|||
}
|
||||
if (m_cursor.column() == 0 && m_cursor.line() != 0) {
|
||||
// Backspace at column 0; merge with previous line
|
||||
auto& previous_line = m_lines[m_cursor.line() - 1];
|
||||
auto& previous_line = lines()[m_cursor.line() - 1];
|
||||
int previous_length = previous_line.length();
|
||||
previous_line.append(current_line().characters(), current_line().length());
|
||||
m_lines.remove(m_cursor.line());
|
||||
lines().remove(m_cursor.line());
|
||||
update_content_size();
|
||||
update();
|
||||
set_cursor(m_cursor.line() - 1, previous_length);
|
||||
|
@ -691,9 +675,9 @@ void GTextEditor::delete_current_line()
|
|||
if (has_selection())
|
||||
return delete_selection();
|
||||
|
||||
m_lines.remove(m_cursor.line());
|
||||
if (m_lines.is_empty())
|
||||
m_lines.append(make<Line>(*this));
|
||||
lines().remove(m_cursor.line());
|
||||
if (lines().is_empty())
|
||||
lines().append(make<GTextDocumentLine>(*this));
|
||||
|
||||
update_content_size();
|
||||
update();
|
||||
|
@ -716,10 +700,10 @@ void GTextEditor::do_delete()
|
|||
}
|
||||
if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
|
||||
// Delete at end of line; merge with next line
|
||||
auto& next_line = m_lines[m_cursor.line() + 1];
|
||||
auto& next_line = lines()[m_cursor.line() + 1];
|
||||
int previous_length = current_line().length();
|
||||
current_line().append(next_line.characters(), next_line.length());
|
||||
m_lines.remove(m_cursor.line() + 1);
|
||||
lines().remove(m_cursor.line() + 1);
|
||||
update();
|
||||
did_change();
|
||||
set_cursor(m_cursor.line(), previous_length);
|
||||
|
@ -744,7 +728,7 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
String new_line_contents;
|
||||
if (m_automatic_indentation_enabled && at_tail) {
|
||||
int leading_spaces = 0;
|
||||
auto& old_line = m_lines[m_cursor.line()];
|
||||
auto& old_line = lines()[m_cursor.line()];
|
||||
for (int i = 0; i < old_line.length(); ++i) {
|
||||
if (old_line.characters()[i] == ' ')
|
||||
++leading_spaces;
|
||||
|
@ -754,16 +738,16 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
if (leading_spaces)
|
||||
new_line_contents = String::repeated(' ', leading_spaces);
|
||||
}
|
||||
m_lines.insert(m_cursor.line() + (at_tail ? 1 : 0), make<Line>(*this, new_line_contents));
|
||||
lines().insert(m_cursor.line() + (at_tail ? 1 : 0), make<GTextDocumentLine>(*this, new_line_contents));
|
||||
update();
|
||||
did_change();
|
||||
set_cursor(m_cursor.line() + 1, m_lines[m_cursor.line() + 1].length());
|
||||
set_cursor(m_cursor.line() + 1, lines()[m_cursor.line() + 1].length());
|
||||
return;
|
||||
}
|
||||
auto new_line = make<Line>(*this);
|
||||
auto new_line = make<GTextDocumentLine>(*this);
|
||||
new_line->append(current_line().characters() + m_cursor.column(), current_line().length() - m_cursor.column());
|
||||
current_line().truncate(m_cursor.column());
|
||||
m_lines.insert(m_cursor.line() + 1, move(new_line));
|
||||
lines().insert(m_cursor.line() + 1, move(new_line));
|
||||
update();
|
||||
did_change();
|
||||
set_cursor(m_cursor.line() + 1, 0);
|
||||
|
@ -786,7 +770,7 @@ void GTextEditor::insert_at_cursor(char ch)
|
|||
|
||||
int GTextEditor::content_x_for_position(const GTextPosition& position) const
|
||||
{
|
||||
auto& line = m_lines[position.line()];
|
||||
auto& line = lines()[position.line()];
|
||||
int x_offset = -1;
|
||||
switch (m_text_alignment) {
|
||||
case TextAlignment::CenterLeft:
|
||||
|
@ -811,7 +795,7 @@ Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
|
|||
{
|
||||
if (!position.is_valid())
|
||||
return {};
|
||||
ASSERT(!m_lines.is_empty());
|
||||
ASSERT(!lines().is_empty());
|
||||
ASSERT(position.column() <= (current_line().length() + 1));
|
||||
|
||||
int x = content_x_for_position(position);
|
||||
|
@ -822,7 +806,7 @@ Rect GTextEditor::content_rect_for_position(const GTextPosition& position) const
|
|||
return rect;
|
||||
}
|
||||
|
||||
auto& line = m_lines[position.line()];
|
||||
auto& line = lines()[position.line()];
|
||||
Rect rect;
|
||||
line.for_each_visual_line([&](const Rect& visual_line_rect, const StringView& view, int start_of_visual_line) {
|
||||
if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
|
||||
|
@ -862,8 +846,8 @@ void GTextEditor::scroll_position_into_view(const GTextPosition& position)
|
|||
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);
|
||||
else if (position.column() == lines()[position.line()].length())
|
||||
rect.set_x(content_x_for_position({ position.line(), lines()[position.line()].length() }) + 2);
|
||||
scroll_into_view(rect, true, true);
|
||||
}
|
||||
|
||||
|
@ -874,7 +858,7 @@ void GTextEditor::scroll_cursor_into_view()
|
|||
|
||||
Rect GTextEditor::line_content_rect(int line_index) const
|
||||
{
|
||||
auto& line = m_lines[line_index];
|
||||
auto& line = lines()[line_index];
|
||||
if (is_single_line()) {
|
||||
Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, line.length() * glyph_width(), font().glyph_height() + 2 };
|
||||
line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
|
||||
|
@ -902,19 +886,19 @@ void GTextEditor::set_cursor(int line, int column)
|
|||
|
||||
void GTextEditor::set_cursor(const GTextPosition& a_position)
|
||||
{
|
||||
ASSERT(!m_lines.is_empty());
|
||||
ASSERT(!lines().is_empty());
|
||||
|
||||
GTextPosition position = a_position;
|
||||
|
||||
if (position.line() >= m_lines.size())
|
||||
position.set_line(m_lines.size() - 1);
|
||||
if (position.line() >= lines().size())
|
||||
position.set_line(lines().size() - 1);
|
||||
|
||||
if (position.column() > m_lines[position.line()].length())
|
||||
position.set_column(m_lines[position.line()].length());
|
||||
if (position.column() > lines()[position.line()].length())
|
||||
position.set_column(lines()[position.line()].length());
|
||||
|
||||
if (m_cursor != position) {
|
||||
// NOTE: If the old cursor is no longer valid, repaint everything just in case.
|
||||
auto old_cursor_line_rect = m_cursor.line() < m_lines.size()
|
||||
auto old_cursor_line_rect = m_cursor.line() < lines().size()
|
||||
? line_widget_rect(m_cursor.line())
|
||||
: rect();
|
||||
m_cursor = position;
|
||||
|
@ -945,79 +929,6 @@ void GTextEditor::timer_event(CTimerEvent&)
|
|||
update_cursor();
|
||||
}
|
||||
|
||||
GTextEditor::Line::Line(GTextEditor& editor)
|
||||
: m_editor(editor)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
GTextEditor::Line::Line(GTextEditor& editor, const StringView& text)
|
||||
: m_editor(editor)
|
||||
{
|
||||
set_text(text);
|
||||
}
|
||||
|
||||
void GTextEditor::Line::clear()
|
||||
{
|
||||
m_text.clear();
|
||||
m_text.append(0);
|
||||
}
|
||||
|
||||
void GTextEditor::Line::set_text(const StringView& text)
|
||||
{
|
||||
if (text.length() == length() && !memcmp(text.characters_without_null_termination(), characters(), length()))
|
||||
return;
|
||||
if (text.is_empty()) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
m_text.resize(text.length() + 1);
|
||||
memcpy(m_text.data(), text.characters_without_null_termination(), text.length() + 1);
|
||||
}
|
||||
|
||||
void GTextEditor::Line::append(const char* characters, int length)
|
||||
{
|
||||
int old_length = m_text.size() - 1;
|
||||
m_text.resize(m_text.size() + length);
|
||||
memcpy(m_text.data() + old_length, characters, length);
|
||||
m_text.last() = 0;
|
||||
}
|
||||
|
||||
void GTextEditor::Line::append(char ch)
|
||||
{
|
||||
insert(length(), ch);
|
||||
}
|
||||
|
||||
void GTextEditor::Line::prepend(char ch)
|
||||
{
|
||||
insert(0, ch);
|
||||
}
|
||||
|
||||
void GTextEditor::Line::insert(int index, char ch)
|
||||
{
|
||||
if (index == length()) {
|
||||
m_text.last() = ch;
|
||||
m_text.append(0);
|
||||
} else {
|
||||
m_text.insert(index, move(ch));
|
||||
}
|
||||
}
|
||||
|
||||
void GTextEditor::Line::remove(int index)
|
||||
{
|
||||
if (index == length()) {
|
||||
m_text.take_last();
|
||||
m_text.last() = 0;
|
||||
} else {
|
||||
m_text.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
void GTextEditor::Line::truncate(int length)
|
||||
{
|
||||
m_text.resize(length + 1);
|
||||
m_text.last() = 0;
|
||||
}
|
||||
|
||||
bool GTextEditor::write_to_file(const StringView& path)
|
||||
{
|
||||
|
@ -1030,9 +941,9 @@ bool GTextEditor::write_to_file(const StringView& path)
|
|||
// Compute the final file size and ftruncate() to make writing fast.
|
||||
// FIXME: Remove this once the kernel is smart enough to do this instead.
|
||||
off_t file_size = 0;
|
||||
for (int i = 0; i < m_lines.size(); ++i)
|
||||
file_size += m_lines[i].length();
|
||||
file_size += m_lines.size() - 1;
|
||||
for (int i = 0; i < lines().size(); ++i)
|
||||
file_size += lines()[i].length();
|
||||
file_size += lines().size() - 1;
|
||||
|
||||
int rc = ftruncate(fd, file_size);
|
||||
if (rc < 0) {
|
||||
|
@ -1040,8 +951,8 @@ bool GTextEditor::write_to_file(const StringView& path)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_lines.size(); ++i) {
|
||||
auto& line = m_lines[i];
|
||||
for (int i = 0; i < lines().size(); ++i) {
|
||||
auto& line = lines()[i];
|
||||
if (line.length()) {
|
||||
ssize_t nwritten = write(fd, line.characters(), line.length());
|
||||
if (nwritten < 0) {
|
||||
|
@ -1050,7 +961,7 @@ bool GTextEditor::write_to_file(const StringView& path)
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (i != m_lines.size() - 1) {
|
||||
if (i != lines().size() - 1) {
|
||||
char ch = '\n';
|
||||
ssize_t nwritten = write(fd, &ch, 1);
|
||||
if (nwritten != 1) {
|
||||
|
@ -1069,7 +980,7 @@ String GTextEditor::text() const
|
|||
{
|
||||
StringBuilder builder;
|
||||
for (int i = 0; i < line_count(); ++i) {
|
||||
auto& line = m_lines[i];
|
||||
auto& line = lines()[i];
|
||||
builder.append(line.characters(), line.length());
|
||||
if (i != line_count() - 1)
|
||||
builder.append('\n');
|
||||
|
@ -1079,8 +990,8 @@ String GTextEditor::text() const
|
|||
|
||||
void GTextEditor::clear()
|
||||
{
|
||||
m_lines.clear();
|
||||
m_lines.append(make<Line>(*this));
|
||||
lines().clear();
|
||||
lines().append(make<GTextDocumentLine>(*this));
|
||||
m_selection.clear();
|
||||
did_update_selection();
|
||||
set_cursor(0, 0);
|
||||
|
@ -1095,7 +1006,7 @@ String GTextEditor::selected_text() const
|
|||
auto selection = normalized_selection();
|
||||
StringBuilder builder;
|
||||
for (int i = selection.start().line(); i <= selection.end().line(); ++i) {
|
||||
auto& line = m_lines[i];
|
||||
auto& line = lines()[i];
|
||||
int selection_start_column_on_line = selection.start().line() == i ? selection.start().column() : 0;
|
||||
int selection_end_column_on_line = selection.end().line() == i ? selection.end().column() : line.length();
|
||||
builder.append(line.characters() + selection_start_column_on_line, selection_end_column_on_line - selection_start_column_on_line);
|
||||
|
@ -1115,13 +1026,13 @@ void GTextEditor::delete_selection()
|
|||
|
||||
// First delete all the lines in between the first and last one.
|
||||
for (int i = selection.start().line() + 1; i < selection.end().line();) {
|
||||
m_lines.remove(i);
|
||||
lines().remove(i);
|
||||
selection.end().set_line(selection.end().line() - 1);
|
||||
}
|
||||
|
||||
if (selection.start().line() == selection.end().line()) {
|
||||
// Delete within same line.
|
||||
auto& line = m_lines[selection.start().line()];
|
||||
auto& line = lines()[selection.start().line()];
|
||||
bool whole_line_is_selected = selection.start().column() == 0 && selection.end().column() == line.length();
|
||||
if (whole_line_is_selected) {
|
||||
line.clear();
|
||||
|
@ -1136,19 +1047,19 @@ void GTextEditor::delete_selection()
|
|||
} else {
|
||||
// Delete across a newline, merging lines.
|
||||
ASSERT(selection.start().line() == selection.end().line() - 1);
|
||||
auto& first_line = m_lines[selection.start().line()];
|
||||
auto& second_line = m_lines[selection.end().line()];
|
||||
auto& first_line = lines()[selection.start().line()];
|
||||
auto& second_line = lines()[selection.end().line()];
|
||||
auto before_selection = String(first_line.characters(), first_line.length()).substring(0, selection.start().column());
|
||||
auto after_selection = String(second_line.characters(), second_line.length()).substring(selection.end().column(), second_line.length() - selection.end().column());
|
||||
StringBuilder builder(before_selection.length() + after_selection.length());
|
||||
builder.append(before_selection);
|
||||
builder.append(after_selection);
|
||||
first_line.set_text(builder.to_string());
|
||||
m_lines.remove(selection.end().line());
|
||||
lines().remove(selection.end().line());
|
||||
}
|
||||
|
||||
if (m_lines.is_empty())
|
||||
m_lines.append(make<Line>(*this));
|
||||
if (lines().is_empty())
|
||||
lines().append(make<GTextDocumentLine>(*this));
|
||||
|
||||
m_selection.clear();
|
||||
did_update_selection();
|
||||
|
@ -1278,7 +1189,7 @@ void GTextEditor::resize_event(GResizeEvent& event)
|
|||
|
||||
GTextPosition GTextEditor::next_position_after(const GTextPosition& position, ShouldWrapAtEndOfDocument should_wrap)
|
||||
{
|
||||
auto& line = m_lines[position.line()];
|
||||
auto& line = lines()[position.line()];
|
||||
if (position.column() == line.length()) {
|
||||
if (position.line() == line_count() - 1) {
|
||||
if (should_wrap == ShouldWrapAtEndOfDocument::Yes)
|
||||
|
@ -1295,12 +1206,12 @@ GTextPosition GTextEditor::prev_position_before(const GTextPosition& position, S
|
|||
if (position.column() == 0) {
|
||||
if (position.line() == 0) {
|
||||
if (should_wrap == ShouldWrapAtStartOfDocument::Yes) {
|
||||
auto& last_line = m_lines[line_count() - 1];
|
||||
auto& last_line = lines()[line_count() - 1];
|
||||
return { line_count() - 1, last_line.length() };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
auto& prev_line = m_lines[position.line() - 1];
|
||||
auto& prev_line = lines()[position.line() - 1];
|
||||
return { position.line() - 1, prev_line.length() };
|
||||
}
|
||||
return { position.line(), position.column() - 1 };
|
||||
|
@ -1380,7 +1291,7 @@ void GTextEditor::set_selection(const GTextRange& selection)
|
|||
char GTextEditor::character_at(const GTextPosition& position) const
|
||||
{
|
||||
ASSERT(position.line() < line_count());
|
||||
auto& line = m_lines[position.line()];
|
||||
auto& line = lines()[position.line()];
|
||||
if (position.column() == line.length())
|
||||
return '\n';
|
||||
return line.characters()[position.column()];
|
||||
|
@ -1389,7 +1300,7 @@ char GTextEditor::character_at(const GTextPosition& position) const
|
|||
void GTextEditor::recompute_all_visual_lines()
|
||||
{
|
||||
int y_offset = 0;
|
||||
for (auto& line : m_lines) {
|
||||
for (auto& line : lines()) {
|
||||
line.recompute_visual_lines();
|
||||
line.m_visual_rect.set_y(y_offset);
|
||||
y_offset += line.m_visual_rect.height();
|
||||
|
@ -1398,7 +1309,19 @@ void GTextEditor::recompute_all_visual_lines()
|
|||
update_content_size();
|
||||
}
|
||||
|
||||
void GTextEditor::Line::recompute_visual_lines()
|
||||
int GTextDocumentLine::visual_line_containing(int column) const
|
||||
{
|
||||
int visual_line_index = 0;
|
||||
for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
||||
if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
|
||||
return IterationDecision::Break;
|
||||
++visual_line_index;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return visual_line_index;
|
||||
}
|
||||
|
||||
void GTextDocumentLine::recompute_visual_lines()
|
||||
{
|
||||
m_visual_line_breaks.clear_with_capacity();
|
||||
|
||||
|
@ -1428,7 +1351,7 @@ void GTextEditor::Line::recompute_visual_lines()
|
|||
}
|
||||
|
||||
template<typename Callback>
|
||||
void GTextEditor::Line::for_each_visual_line(Callback callback) const
|
||||
void GTextDocumentLine::for_each_visual_line(Callback callback) const
|
||||
{
|
||||
auto editor_visible_text_rect = m_editor.visible_text_rect_in_inner_coordinates();
|
||||
int start_of_line = 0;
|
||||
|
@ -1464,27 +1387,6 @@ void GTextEditor::set_line_wrapping_enabled(bool enabled)
|
|||
update();
|
||||
}
|
||||
|
||||
int GTextEditor::Line::visual_line_containing(int column) const
|
||||
{
|
||||
int visual_line_index = 0;
|
||||
for_each_visual_line([&](const Rect&, const StringView& view, int start_of_visual_line) {
|
||||
if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
|
||||
return IterationDecision::Break;
|
||||
++visual_line_index;
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return visual_line_index;
|
||||
}
|
||||
|
||||
int GTextEditor::Line::first_non_whitespace_column() const
|
||||
{
|
||||
for (int i = 0; i < length(); ++i) {
|
||||
if (!isspace(m_text[i]))
|
||||
return i;
|
||||
}
|
||||
return length();
|
||||
}
|
||||
|
||||
void GTextEditor::add_custom_context_menu_action(GAction& action)
|
||||
{
|
||||
m_custom_context_menu_actions.append(action);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue