Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/QuickSort.h>
28#include <AK/StringBuilder.h>
29#include <Kernel/KeyCode.h>
30#include <LibGUI/Action.h>
31#include <LibGUI/Clipboard.h>
32#include <LibGUI/FontDatabase.h>
33#include <LibGUI/InputBox.h>
34#include <LibGUI/Menu.h>
35#include <LibGUI/Painter.h>
36#include <LibGUI/ScrollBar.h>
37#include <LibGUI/SyntaxHighlighter.h>
38#include <LibGUI/TextEditor.h>
39#include <LibGUI/Window.h>
40#include <LibGfx/Bitmap.h>
41#include <LibGfx/Font.h>
42#include <LibGfx/Palette.h>
43#include <ctype.h>
44#include <fcntl.h>
45#include <stdio.h>
46#include <unistd.h>
47
48//#define DEBUG_TEXTEDITOR
49
50namespace GUI {
51
52TextEditor::TextEditor(Type type)
53 : m_type(type)
54{
55 set_background_role(ColorRole::Base);
56 set_foreground_role(ColorRole::BaseText);
57 set_document(TextDocument::create());
58 set_scrollbars_enabled(is_multi_line());
59 set_font(FontDatabase::the().get_by_name("Csilla Thin"));
60 // FIXME: Recompute vertical scrollbar step size on font change.
61 vertical_scrollbar().set_step(line_height());
62 m_cursor = { 0, 0 };
63 create_actions();
64}
65
66TextEditor::~TextEditor()
67{
68 if (m_document)
69 m_document->unregister_client(*this);
70}
71
72void TextEditor::create_actions()
73{
74 m_undo_action = CommonActions::make_undo_action([&](auto&) { undo(); }, this);
75 m_redo_action = CommonActions::make_redo_action([&](auto&) { redo(); }, this);
76 m_undo_action->set_enabled(false);
77 m_redo_action->set_enabled(false);
78 m_cut_action = CommonActions::make_cut_action([&](auto&) { cut(); }, this);
79 m_copy_action = CommonActions::make_copy_action([&](auto&) { copy(); }, this);
80 m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this);
81 m_delete_action = CommonActions::make_delete_action([&](auto&) { do_delete(); }, this);
82 if (is_multi_line()) {
83 m_go_to_line_action = Action::create(
84 "Go to line...", { Mod_Ctrl, Key_L }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](auto&) {
85 auto input_box = InputBox::construct("Line:", "Go to line", window());
86 auto result = input_box->exec();
87 if (result == InputBox::ExecOK) {
88 bool ok;
89 auto line_number = input_box->text_value().to_uint(ok);
90 if (ok)
91 set_cursor(line_number - 1, 0);
92 }
93 },
94 this);
95 }
96}
97
98void TextEditor::set_text(const StringView& text)
99{
100 if (is_single_line() && text.length() == line(0).length() && !memcmp(text.characters_without_null_termination(), line(0).characters(), text.length()))
101 return;
102
103 m_selection.clear();
104
105 document().set_text(text);
106
107 update_content_size();
108 recompute_all_visual_lines();
109 if (is_single_line())
110 set_cursor(0, line(0).length());
111 else
112 set_cursor(0, 0);
113 did_update_selection();
114 update();
115}
116
117void TextEditor::update_content_size()
118{
119 int content_width = 0;
120 int content_height = 0;
121 for (auto& line : m_line_visual_data) {
122 content_width = max(line.visual_rect.width(), content_width);
123 content_height += line.visual_rect.height();
124 }
125 content_width += m_horizontal_content_padding * 2;
126 if (is_right_text_alignment(m_text_alignment))
127 content_width = max(frame_inner_rect().width(), content_width);
128
129 set_content_size({ content_width, content_height });
130 set_size_occupied_by_fixed_elements({ ruler_width(), 0 });
131}
132
133TextPosition TextEditor::text_position_at(const Gfx::Point& a_position) const
134{
135 auto position = a_position;
136 position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
137 position.move_by(-(m_horizontal_content_padding + ruler_width()), 0);
138 position.move_by(-frame_thickness(), -frame_thickness());
139
140 size_t line_index = 0;
141
142 if (is_line_wrapping_enabled()) {
143 for (size_t i = 0; i < line_count(); ++i) {
144 auto& rect = m_line_visual_data[i].visual_rect;
145 if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
146 line_index = i;
147 break;
148 }
149 if (position.y() > rect.bottom())
150 line_index = line_count() - 1;
151 }
152 } else {
153 line_index = (size_t)(position.y() / line_height());
154 }
155
156 line_index = max((size_t)0, min(line_index, line_count() - 1));
157
158 size_t column_index;
159 switch (m_text_alignment) {
160 case Gfx::TextAlignment::CenterLeft:
161 column_index = (position.x() + glyph_width() / 2) / glyph_width();
162 if (is_line_wrapping_enabled()) {
163 for_each_visual_line(line_index, [&](const Gfx::Rect& rect, const StringView&, size_t start_of_line) {
164 if (rect.contains_vertically(position.y())) {
165 column_index += start_of_line;
166 return IterationDecision::Break;
167 }
168 return IterationDecision::Continue;
169 });
170 }
171 break;
172 case Gfx::TextAlignment::CenterRight:
173 // FIXME: Support right-aligned line wrapping, I guess.
174 ASSERT(!is_line_wrapping_enabled());
175 column_index = (position.x() - content_x_for_position({ line_index, 0 }) + glyph_width() / 2) / glyph_width();
176 break;
177 default:
178 ASSERT_NOT_REACHED();
179 }
180
181 column_index = max((size_t)0, min(column_index, line(line_index).length()));
182 return { line_index, column_index };
183}
184
185void TextEditor::doubleclick_event(MouseEvent& event)
186{
187 if (event.button() != MouseButton::Left)
188 return;
189
190 // NOTE: This ensures that spans are updated before we look at them.
191 flush_pending_change_notification_if_needed();
192
193 m_triple_click_timer.start();
194 m_in_drag_select = false;
195
196 auto start = text_position_at(event.position());
197 auto end = start;
198 auto& line = this->line(start.line());
199
200 if (!document().has_spans()) {
201 while (start.column() > 0) {
202 if (isspace(line.characters()[start.column() - 1]))
203 break;
204 start.set_column(start.column() - 1);
205 }
206
207 while (end.column() < line.length()) {
208 if (isspace(line.characters()[end.column()]))
209 break;
210 end.set_column(end.column() + 1);
211 }
212 } else {
213 for (auto& span : document().spans()) {
214 if (!span.range.contains(start))
215 continue;
216 start = span.range.start();
217 end = span.range.end();
218 end.set_column(end.column() + 1);
219 break;
220 }
221 }
222
223 m_selection.set(start, end);
224 set_cursor(end);
225 update();
226 did_update_selection();
227}
228
229void TextEditor::mousedown_event(MouseEvent& event)
230{
231 if (event.button() != MouseButton::Left) {
232 return;
233 }
234
235 if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) {
236 m_triple_click_timer = Core::ElapsedTimer();
237
238 TextPosition start;
239 TextPosition end;
240
241 if (is_multi_line()) {
242 // select *current* line
243 start = TextPosition(m_cursor.line(), 0);
244 end = TextPosition(m_cursor.line(), line(m_cursor.line()).length());
245 } else {
246 // select *whole* line
247 start = TextPosition(0, 0);
248 end = TextPosition(line_count() - 1, line(line_count() - 1).length());
249 }
250
251 m_selection.set(start, end);
252 set_cursor(end);
253 return;
254 }
255
256 if (event.modifiers() & Mod_Shift) {
257 if (!has_selection())
258 m_selection.set(m_cursor, {});
259 } else {
260 m_selection.clear();
261 }
262
263 m_in_drag_select = true;
264
265 set_cursor(text_position_at(event.position()));
266
267 if (!(event.modifiers() & Mod_Shift)) {
268 if (!has_selection())
269 m_selection.set(m_cursor, {});
270 }
271
272 if (m_selection.start().is_valid() && m_selection.start() != m_cursor)
273 m_selection.set_end(m_cursor);
274
275 // FIXME: Only update the relevant rects.
276 update();
277 did_update_selection();
278}
279
280void TextEditor::mouseup_event(MouseEvent& event)
281{
282 if (event.button() == MouseButton::Left) {
283 if (m_in_drag_select) {
284 m_in_drag_select = false;
285 }
286 return;
287 }
288}
289
290void TextEditor::mousemove_event(MouseEvent& event)
291{
292 if (m_in_drag_select) {
293 set_cursor(text_position_at(event.position()));
294 m_selection.set_end(m_cursor);
295 did_update_selection();
296 update();
297 return;
298 }
299}
300
301int TextEditor::ruler_width() const
302{
303 if (!m_ruler_visible)
304 return 0;
305 // FIXME: Resize based on needed space.
306 return 5 * font().glyph_width('x') + 4;
307}
308
309Gfx::Rect TextEditor::ruler_content_rect(size_t line_index) const
310{
311 if (!m_ruler_visible)
312 return {};
313 return {
314 0 - ruler_width() + horizontal_scrollbar().value(),
315 line_content_rect(line_index).y(),
316 ruler_width(),
317 line_content_rect(line_index).height()
318 };
319}
320
321Gfx::Rect TextEditor::ruler_rect_in_inner_coordinates() const
322{
323 return { 0, 0, ruler_width(), height() - height_occupied_by_horizontal_scrollbar() };
324}
325
326Gfx::Rect TextEditor::visible_text_rect_in_inner_coordinates() const
327{
328 return {
329 m_horizontal_content_padding + (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + 1) : 0),
330 0,
331 frame_inner_rect().width() - (m_horizontal_content_padding * 2) - width_occupied_by_vertical_scrollbar() - ruler_width(),
332 frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar()
333 };
334}
335
336void TextEditor::paint_event(PaintEvent& event)
337{
338 Color widget_background_color = palette().color(background_role());
339 // NOTE: This ensures that spans are updated before we look at them.
340 flush_pending_change_notification_if_needed();
341
342 Frame::paint_event(event);
343
344 Painter painter(*this);
345 painter.add_clip_rect(widget_inner_rect());
346 painter.add_clip_rect(event.rect());
347 painter.fill_rect(event.rect(), widget_background_color);
348
349 painter.translate(frame_thickness(), frame_thickness());
350
351 auto ruler_rect = ruler_rect_in_inner_coordinates();
352
353 if (m_ruler_visible) {
354 painter.fill_rect(ruler_rect, palette().ruler());
355 painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), palette().ruler_border());
356 }
357
358 painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value());
359 if (m_ruler_visible)
360 painter.translate(ruler_width(), 0);
361
362 size_t first_visible_line = text_position_at(event.rect().top_left()).line();
363 size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
364
365 auto selection = normalized_selection();
366 bool has_selection = selection.is_valid();
367
368 if (m_ruler_visible) {
369 for (size_t i = first_visible_line; i <= last_visible_line; ++i) {
370 bool is_current_line = i == m_cursor.line();
371 auto ruler_line_rect = ruler_content_rect(i);
372 painter.draw_text(
373 ruler_line_rect.shrunken(2, 0).translated(0, m_line_spacing / 2),
374 String::number(i + 1),
375 is_current_line ? Gfx::Font::default_bold_font() : font(),
376 Gfx::TextAlignment::TopRight,
377 is_current_line ? palette().ruler_active_text() : palette().ruler_inactive_text());
378 }
379 }
380
381 Gfx::Rect text_clip_rect {
382 (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + frame_thickness() + 1) : frame_thickness()),
383 frame_thickness(),
384 width() - width_occupied_by_vertical_scrollbar() - ruler_width(),
385 height() - height_occupied_by_horizontal_scrollbar()
386 };
387 painter.add_clip_rect(text_clip_rect);
388
389 for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
390 auto& line = this->line(line_index);
391
392 bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
393 size_t first_visual_line_with_selection = 0;
394 size_t last_visual_line_with_selection = 0;
395 if (physical_line_has_selection) {
396 if (selection.start().line() < line_index)
397 first_visual_line_with_selection = 0;
398 else
399 first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column());
400
401 if (selection.end().line() > line_index)
402 last_visual_line_with_selection = m_line_visual_data[line_index].visual_line_breaks.size();
403 else
404 last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column());
405 }
406
407 size_t selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
408 size_t selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
409
410 size_t visual_line_index = 0;
411 for_each_visual_line(line_index, [&](const Gfx::Rect& visual_line_rect, const StringView& visual_line_text, size_t start_of_visual_line) {
412 if (is_multi_line() && line_index == m_cursor.line())
413 painter.fill_rect(visual_line_rect, widget_background_color.darkened(0.9f));
414#ifdef DEBUG_TEXTEDITOR
415 painter.draw_rect(visual_line_rect, Color::Cyan);
416#endif
417 if (!document().has_spans()) {
418 // Fast-path for plain text
419 auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText);
420 painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color);
421 } else {
422 int advance = font().glyph_width(' ') + font().glyph_spacing();
423 Gfx::Rect character_rect = { visual_line_rect.location(), { font().glyph_width(' '), line_height() } };
424 for (size_t i = 0; i < visual_line_text.length(); ++i) {
425 const Gfx::Font* font = &this->font();
426 Color color;
427 Optional<Color> background_color;
428 bool underline = false;
429 TextPosition physical_position(line_index, start_of_visual_line + i);
430 // FIXME: This is *horribly* inefficient.
431 for (auto& span : document().spans()) {
432 if (!span.range.contains(physical_position))
433 continue;
434 color = span.color;
435 if (span.font)
436 font = span.font;
437 background_color = span.background_color;
438 underline = span.is_underlined;
439 break;
440 }
441 if (background_color.has_value())
442 painter.fill_rect(character_rect, background_color.value());
443 painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color);
444 if (underline) {
445 painter.draw_line(character_rect.bottom_left().translated(0, 1), character_rect.bottom_right().translated(0, 1), color);
446 }
447 character_rect.move_by(advance, 0);
448 }
449 }
450 bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
451 if (physical_line_has_selection) {
452
453 bool current_visual_line_has_selection = (line_index != selection.start().line() && line_index != selection.end().line())
454 || (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection);
455 if (current_visual_line_has_selection) {
456 bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection;
457 bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection;
458
459 int selection_left = selection_begins_on_current_visual_line
460 ? content_x_for_position({ line_index, (size_t)selection_start_column_within_line })
461 : m_horizontal_content_padding;
462
463 int selection_right = selection_ends_on_current_visual_line
464 ? content_x_for_position({ line_index, (size_t)selection_end_column_within_line })
465 : visual_line_rect.right() + 1;
466
467 Gfx::Rect selection_rect {
468 selection_left,
469 visual_line_rect.y(),
470 selection_right - selection_left,
471 visual_line_rect.height()
472 };
473
474 Color background_color = is_focused() ? palette().selection() : palette().inactive_selection();
475 Color text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
476
477 painter.fill_rect(selection_rect, background_color);
478
479 size_t start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line);
480 size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line;
481
482 StringView visual_selected_text {
483 visual_line_text.characters_without_null_termination() + start_of_selection_within_visual_line,
484 end_of_selection_within_visual_line - start_of_selection_within_visual_line
485 };
486
487 painter.draw_text(selection_rect, visual_selected_text, Gfx::TextAlignment::CenterLeft, text_color);
488 }
489 }
490 ++visual_line_index;
491 return IterationDecision::Continue;
492 });
493 }
494
495 if (is_focused() && m_cursor_state)
496 painter.fill_rect(cursor_content_rect(), palette().text_cursor());
497}
498
499void TextEditor::toggle_selection_if_needed_for_event(const KeyEvent& event)
500{
501 if (event.shift() && !m_selection.is_valid()) {
502 m_selection.set(m_cursor, {});
503 did_update_selection();
504 update();
505 return;
506 }
507 if (!event.shift() && m_selection.is_valid()) {
508 m_selection.clear();
509 did_update_selection();
510 update();
511 return;
512 }
513}
514
515void TextEditor::select_all()
516{
517 TextPosition start_of_document { 0, 0 };
518 TextPosition end_of_document { line_count() - 1, line(line_count() - 1).length() };
519 m_selection.set(start_of_document, end_of_document);
520 did_update_selection();
521 set_cursor(end_of_document);
522 update();
523}
524
525void TextEditor::get_selection_line_boundaries(size_t& first_line, size_t& last_line)
526{
527 auto selection = normalized_selection();
528 if (!selection.is_valid()) {
529 first_line = m_cursor.line();
530 last_line = m_cursor.line();
531 return;
532 }
533 first_line = selection.start().line();
534 last_line = selection.end().line();
535 if (first_line != last_line && selection.end().column() == 0)
536 last_line -= 1;
537}
538
539void TextEditor::move_selected_lines_up()
540{
541 size_t first_line;
542 size_t last_line;
543 get_selection_line_boundaries(first_line, last_line);
544
545 if (first_line == 0)
546 return;
547
548 auto& lines = document().lines();
549 lines.insert((int)last_line, lines.take((int)first_line - 1));
550 m_cursor = { first_line - 1, 0 };
551
552 if (has_selection()) {
553 m_selection.set_start({ first_line - 1, 0 });
554 m_selection.set_end({ last_line - 1, line(last_line - 1).length() });
555 }
556
557 did_change();
558 update();
559}
560
561void TextEditor::move_selected_lines_down()
562{
563 size_t first_line;
564 size_t last_line;
565 get_selection_line_boundaries(first_line, last_line);
566
567 auto& lines = document().lines();
568 ASSERT(lines.size() != 0);
569 if (last_line >= lines.size() - 1)
570 return;
571
572 lines.insert((int)first_line, lines.take((int)last_line + 1));
573 m_cursor = { first_line + 1, 0 };
574
575 if (has_selection()) {
576 m_selection.set_start({ first_line + 1, 0 });
577 m_selection.set_end({ last_line + 1, line(last_line + 1).length() });
578 }
579
580 did_change();
581 update();
582}
583
584void TextEditor::sort_selected_lines()
585{
586 if (is_readonly())
587 return;
588
589 if (!has_selection())
590 return;
591
592 size_t first_line;
593 size_t last_line;
594 get_selection_line_boundaries(first_line, last_line);
595
596 auto& lines = document().lines();
597
598 auto start = lines.begin() + (int)first_line;
599 auto end = lines.begin() + (int)last_line + 1;
600
601 quick_sort(start, end, [](auto& a, auto& b) {
602 return strcmp(a.characters(), b.characters()) < 0;
603 });
604
605 did_change();
606 update();
607}
608
609void TextEditor::keydown_event(KeyEvent& event)
610{
611 if (is_single_line() && event.key() == KeyCode::Key_Tab)
612 return Widget::keydown_event(event);
613
614 if (is_single_line() && event.key() == KeyCode::Key_Return) {
615 if (on_return_pressed)
616 on_return_pressed();
617 return;
618 }
619
620 if (event.key() == KeyCode::Key_Escape) {
621 if (on_escape_pressed)
622 on_escape_pressed();
623 return;
624 }
625 if (is_multi_line() && event.key() == KeyCode::Key_Up) {
626 if (m_cursor.line() > 0) {
627 if (event.ctrl() && event.shift()) {
628 move_selected_lines_up();
629 return;
630 }
631 size_t new_line = m_cursor.line() - 1;
632 size_t new_column = min(m_cursor.column(), line(new_line).length());
633 toggle_selection_if_needed_for_event(event);
634 set_cursor(new_line, new_column);
635 if (event.shift() && m_selection.start().is_valid()) {
636 m_selection.set_end(m_cursor);
637 did_update_selection();
638 }
639 }
640 return;
641 }
642 if (is_multi_line() && event.key() == KeyCode::Key_Down) {
643 if (m_cursor.line() < (line_count() - 1)) {
644 if (event.ctrl() && event.shift()) {
645 move_selected_lines_down();
646 return;
647 }
648 size_t new_line = m_cursor.line() + 1;
649 size_t new_column = min(m_cursor.column(), line(new_line).length());
650 toggle_selection_if_needed_for_event(event);
651 set_cursor(new_line, new_column);
652 if (event.shift() && m_selection.start().is_valid()) {
653 m_selection.set_end(m_cursor);
654 did_update_selection();
655 }
656 }
657 return;
658 }
659 if (is_multi_line() && event.key() == KeyCode::Key_PageUp) {
660 if (m_cursor.line() > 0) {
661 size_t page_step = (size_t)visible_content_rect().height() / (size_t)line_height();
662 size_t new_line = m_cursor.line() < page_step ? 0 : m_cursor.line() - page_step;
663 size_t new_column = min(m_cursor.column(), line(new_line).length());
664 toggle_selection_if_needed_for_event(event);
665 set_cursor(new_line, new_column);
666 if (event.shift() && m_selection.start().is_valid()) {
667 m_selection.set_end(m_cursor);
668 did_update_selection();
669 }
670 }
671 return;
672 }
673 if (is_multi_line() && event.key() == KeyCode::Key_PageDown) {
674 if (m_cursor.line() < (line_count() - 1)) {
675 int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height());
676 int new_column = min(m_cursor.column(), lines()[new_line].length());
677 toggle_selection_if_needed_for_event(event);
678 set_cursor(new_line, new_column);
679 if (event.shift() && m_selection.start().is_valid()) {
680 m_selection.set_end(m_cursor);
681 did_update_selection();
682 }
683 }
684 return;
685 }
686 if (event.key() == KeyCode::Key_Left) {
687 if (event.ctrl() && document().has_spans()) {
688 // FIXME: Do something nice when the document has no spans.
689 auto span = document().first_non_skippable_span_before(m_cursor);
690 TextPosition new_cursor = !span.has_value()
691 ? TextPosition(0, 0)
692 : span.value().range.start();
693 toggle_selection_if_needed_for_event(event);
694 set_cursor(new_cursor);
695 if (event.shift() && m_selection.start().is_valid()) {
696 m_selection.set_end(m_cursor);
697 did_update_selection();
698 }
699 return;
700 }
701 if (m_cursor.column() > 0) {
702 int new_column = m_cursor.column() - 1;
703 toggle_selection_if_needed_for_event(event);
704 set_cursor(m_cursor.line(), new_column);
705 if (event.shift() && m_selection.start().is_valid()) {
706 m_selection.set_end(m_cursor);
707 did_update_selection();
708 }
709 } else if (m_cursor.line() > 0) {
710 int new_line = m_cursor.line() - 1;
711 int new_column = lines()[new_line].length();
712 toggle_selection_if_needed_for_event(event);
713 set_cursor(new_line, new_column);
714 if (event.shift() && m_selection.start().is_valid()) {
715 m_selection.set_end(m_cursor);
716 did_update_selection();
717 }
718 }
719 return;
720 }
721 if (event.key() == KeyCode::Key_Right) {
722 if (event.ctrl() && document().has_spans()) {
723 // FIXME: Do something nice when the document has no spans.
724 auto span = document().first_non_skippable_span_after(m_cursor);
725 TextPosition new_cursor = !span.has_value()
726 ? document().spans().last().range.end()
727 : span.value().range.start();
728 toggle_selection_if_needed_for_event(event);
729 set_cursor(new_cursor);
730 if (event.shift() && m_selection.start().is_valid()) {
731 m_selection.set_end(m_cursor);
732 did_update_selection();
733 }
734 return;
735 }
736 int new_line = m_cursor.line();
737 int new_column = m_cursor.column();
738 if (m_cursor.column() < current_line().length()) {
739 new_line = m_cursor.line();
740 new_column = m_cursor.column() + 1;
741 } else if (m_cursor.line() != line_count() - 1) {
742 new_line = m_cursor.line() + 1;
743 new_column = 0;
744 }
745 toggle_selection_if_needed_for_event(event);
746 set_cursor(new_line, new_column);
747 if (event.shift() && m_selection.start().is_valid()) {
748 m_selection.set_end(m_cursor);
749 did_update_selection();
750 }
751 return;
752 }
753 if (!event.ctrl() && event.key() == KeyCode::Key_Home) {
754 size_t first_nonspace_column = current_line().first_non_whitespace_column();
755 toggle_selection_if_needed_for_event(event);
756 if (m_cursor.column() == first_nonspace_column)
757 set_cursor(m_cursor.line(), 0);
758 else
759 set_cursor(m_cursor.line(), first_nonspace_column);
760 if (event.shift() && m_selection.start().is_valid()) {
761 m_selection.set_end(m_cursor);
762 did_update_selection();
763 }
764 return;
765 }
766 if (!event.ctrl() && event.key() == KeyCode::Key_End) {
767 toggle_selection_if_needed_for_event(event);
768 set_cursor(m_cursor.line(), current_line().length());
769 if (event.shift() && m_selection.start().is_valid()) {
770 m_selection.set_end(m_cursor);
771 did_update_selection();
772 }
773 return;
774 }
775 if (event.ctrl() && event.key() == KeyCode::Key_Home) {
776 toggle_selection_if_needed_for_event(event);
777 set_cursor(0, 0);
778 if (event.shift() && m_selection.start().is_valid()) {
779 m_selection.set_end(m_cursor);
780 did_update_selection();
781 }
782 return;
783 }
784 if (event.ctrl() && event.key() == KeyCode::Key_End) {
785 toggle_selection_if_needed_for_event(event);
786 set_cursor(line_count() - 1, lines()[line_count() - 1].length());
787 if (event.shift() && m_selection.start().is_valid()) {
788 m_selection.set_end(m_cursor);
789 did_update_selection();
790 }
791 return;
792 }
793 if (event.modifiers() == Mod_Ctrl && event.key() == KeyCode::Key_A) {
794 select_all();
795 return;
796 }
797 if (event.alt() && event.shift() && event.key() == KeyCode::Key_S) {
798 sort_selected_lines();
799 return;
800 }
801 if (event.key() == KeyCode::Key_Backspace) {
802 if (is_readonly())
803 return;
804 if (has_selection()) {
805 delete_selection();
806 did_update_selection();
807 return;
808 }
809 if (m_cursor.column() > 0) {
810 int erase_count = 1;
811 if (current_line().first_non_whitespace_column() >= m_cursor.column()) {
812 int new_column;
813 if (m_cursor.column() % m_soft_tab_width == 0)
814 new_column = m_cursor.column() - m_soft_tab_width;
815 else
816 new_column = (m_cursor.column() / m_soft_tab_width) * m_soft_tab_width;
817 erase_count = m_cursor.column() - new_column;
818 }
819
820 // Backspace within line
821 TextRange erased_range({ m_cursor.line(), m_cursor.column() - erase_count }, m_cursor);
822 auto erased_text = document().text_in_range(erased_range);
823 execute<RemoveTextCommand>(erased_text, erased_range);
824 return;
825 }
826 if (m_cursor.column() == 0 && m_cursor.line() != 0) {
827 // Backspace at column 0; merge with previous line
828 size_t previous_length = line(m_cursor.line() - 1).length();
829 TextRange erased_range({ m_cursor.line() - 1, previous_length }, m_cursor);
830 execute<RemoveTextCommand>("\n", erased_range);
831 return;
832 }
833 return;
834 }
835
836 if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) {
837 if (is_readonly())
838 return;
839 delete_current_line();
840 return;
841 }
842
843 if (event.key() == KeyCode::Key_Delete) {
844 if (is_readonly())
845 return;
846 do_delete();
847 return;
848 }
849
850 if (!is_readonly() && !event.ctrl() && !event.alt() && !event.text().is_empty()) {
851 insert_at_cursor_or_replace_selection(event.text());
852 return;
853 }
854
855 event.ignore();
856}
857
858void TextEditor::delete_current_line()
859{
860 if (has_selection())
861 return delete_selection();
862
863 TextPosition start;
864 TextPosition end;
865 if (m_cursor.line() == 0 && line_count() == 1) {
866 start = { 0, 0 };
867 end = { 0, line(0).length() };
868 } else if (m_cursor.line() == line_count() - 1) {
869 start = { m_cursor.line() - 1, line(m_cursor.line()).length() };
870 end = { m_cursor.line(), line(m_cursor.line()).length() };
871 } else {
872 start = { m_cursor.line(), 0 };
873 end = { m_cursor.line() + 1, 0 };
874 }
875
876 TextRange erased_range(start, end);
877 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
878}
879
880void TextEditor::do_delete()
881{
882 if (is_readonly())
883 return;
884
885 if (has_selection())
886 return delete_selection();
887
888 if (m_cursor.column() < current_line().length()) {
889 // Delete within line
890 TextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + 1 });
891 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
892 return;
893 }
894 if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
895 // Delete at end of line; merge with next line
896 TextRange erased_range(m_cursor, { m_cursor.line() + 1, 0 });
897 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
898 return;
899 }
900}
901
902int TextEditor::content_x_for_position(const TextPosition& position) const
903{
904 auto& line = this->line(position.line());
905 int x_offset = -1;
906 switch (m_text_alignment) {
907 case Gfx::TextAlignment::CenterLeft:
908 for_each_visual_line(position.line(), [&](const Gfx::Rect&, const StringView& view, size_t start_of_visual_line) {
909 if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
910 x_offset = (position.column() - start_of_visual_line) * glyph_width();
911 return IterationDecision::Break;
912 }
913 return IterationDecision::Continue;
914 });
915 return m_horizontal_content_padding + x_offset;
916 case Gfx::TextAlignment::CenterRight:
917 // FIXME
918 ASSERT(!is_line_wrapping_enabled());
919 return content_width() - m_horizontal_content_padding - (line.length() * glyph_width()) + (position.column() * glyph_width());
920 default:
921 ASSERT_NOT_REACHED();
922 }
923}
924
925Gfx::Rect TextEditor::content_rect_for_position(const TextPosition& position) const
926{
927 if (!position.is_valid())
928 return {};
929 ASSERT(!lines().is_empty());
930 ASSERT(position.column() <= (current_line().length() + 1));
931
932 int x = content_x_for_position(position);
933
934 if (is_single_line()) {
935 Gfx::Rect rect { x, 0, 1, font().glyph_height() + 2 };
936 rect.center_vertically_within({ {}, frame_inner_rect().size() });
937 return rect;
938 }
939
940 Gfx::Rect rect;
941 for_each_visual_line(position.line(), [&](const Gfx::Rect& visual_line_rect, const StringView& view, size_t start_of_visual_line) {
942 if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) {
943 // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
944 // *and* included in what we get from content_x_for_position().
945 rect = {
946 visual_line_rect.x() + x - (m_horizontal_content_padding),
947 visual_line_rect.y(),
948 1,
949 line_height()
950 };
951 return IterationDecision::Break;
952 }
953 return IterationDecision::Continue;
954 });
955 return rect;
956}
957
958Gfx::Rect TextEditor::cursor_content_rect() const
959{
960 return content_rect_for_position(m_cursor);
961}
962
963Gfx::Rect TextEditor::line_widget_rect(size_t line_index) const
964{
965 auto rect = line_content_rect(line_index);
966 rect.set_x(frame_thickness());
967 rect.set_width(frame_inner_rect().width());
968 rect.move_by(0, -(vertical_scrollbar().value()));
969 rect.move_by(0, frame_thickness());
970 rect.intersect(frame_inner_rect());
971 return rect;
972}
973
974void TextEditor::scroll_position_into_view(const TextPosition& position)
975{
976 auto rect = content_rect_for_position(position);
977 if (position.column() == 0)
978 rect.set_x(content_x_for_position({ position.line(), 0 }) - 2);
979 else if (position.column() == line(position.line()).length())
980 rect.set_x(content_x_for_position({ position.line(), line(position.line()).length() }) + 2);
981 scroll_into_view(rect, true, true);
982}
983
984void TextEditor::scroll_cursor_into_view()
985{
986 scroll_position_into_view(m_cursor);
987}
988
989Gfx::Rect TextEditor::line_content_rect(size_t line_index) const
990{
991 auto& line = this->line(line_index);
992 if (is_single_line()) {
993 Gfx::Rect line_rect = { content_x_for_position({ line_index, 0 }), 0, (int)line.length() * glyph_width(), font().glyph_height() + 2 };
994 line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
995 return line_rect;
996 }
997 if (is_line_wrapping_enabled())
998 return m_line_visual_data[line_index].visual_rect;
999 return {
1000 content_x_for_position({ line_index, 0 }),
1001 (int)line_index * line_height(),
1002 (int)line.length() * glyph_width(),
1003 line_height()
1004 };
1005}
1006
1007void TextEditor::update_cursor()
1008{
1009 update(line_widget_rect(m_cursor.line()));
1010}
1011
1012void TextEditor::set_cursor(size_t line, size_t column)
1013{
1014 set_cursor({ line, column });
1015}
1016
1017void TextEditor::set_cursor(const TextPosition& a_position)
1018{
1019 ASSERT(!lines().is_empty());
1020
1021 TextPosition position = a_position;
1022
1023 if (position.line() >= line_count())
1024 position.set_line(line_count() - 1);
1025
1026 if (position.column() > lines()[position.line()].length())
1027 position.set_column(lines()[position.line()].length());
1028
1029 if (m_cursor != position) {
1030 // NOTE: If the old cursor is no longer valid, repaint everything just in case.
1031 auto old_cursor_line_rect = m_cursor.line() < line_count()
1032 ? line_widget_rect(m_cursor.line())
1033 : rect();
1034 m_cursor = position;
1035 m_cursor_state = true;
1036 scroll_cursor_into_view();
1037 update(old_cursor_line_rect);
1038 update_cursor();
1039 }
1040 cursor_did_change();
1041 if (on_cursor_change)
1042 on_cursor_change();
1043 if (m_highlighter)
1044 m_highlighter->cursor_did_change();
1045}
1046
1047void TextEditor::focusin_event(Core::Event&)
1048{
1049 update_cursor();
1050 start_timer(500);
1051}
1052
1053void TextEditor::focusout_event(Core::Event&)
1054{
1055 stop_timer();
1056}
1057
1058void TextEditor::timer_event(Core::TimerEvent&)
1059{
1060 m_cursor_state = !m_cursor_state;
1061 if (is_focused())
1062 update_cursor();
1063}
1064
1065bool TextEditor::write_to_file(const StringView& path)
1066{
1067 int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
1068 if (fd < 0) {
1069 perror("open");
1070 return false;
1071 }
1072
1073 // Compute the final file size and ftruncate() to make writing fast.
1074 // FIXME: Remove this once the kernel is smart enough to do this instead.
1075 off_t file_size = 0;
1076 for (size_t i = 0; i < line_count(); ++i)
1077 file_size += line(i).length();
1078 file_size += line_count() - 1;
1079
1080 int rc = ftruncate(fd, file_size);
1081 if (rc < 0) {
1082 perror("ftruncate");
1083 return false;
1084 }
1085
1086 for (size_t i = 0; i < line_count(); ++i) {
1087 auto& line = this->line(i);
1088 if (line.length()) {
1089 ssize_t nwritten = write(fd, line.characters(), line.length());
1090 if (nwritten < 0) {
1091 perror("write");
1092 close(fd);
1093 return false;
1094 }
1095 }
1096 if (i != line_count() - 1) {
1097 char ch = '\n';
1098 ssize_t nwritten = write(fd, &ch, 1);
1099 if (nwritten != 1) {
1100 perror("write");
1101 close(fd);
1102 return false;
1103 }
1104 }
1105 }
1106
1107 close(fd);
1108 return true;
1109}
1110
1111String TextEditor::text() const
1112{
1113 StringBuilder builder;
1114 for (size_t i = 0; i < line_count(); ++i) {
1115 auto& line = this->line(i);
1116 builder.append(line.characters(), line.length());
1117 if (i != line_count() - 1)
1118 builder.append('\n');
1119 }
1120 return builder.to_string();
1121}
1122
1123void TextEditor::clear()
1124{
1125 document().remove_all_lines();
1126 document().append_line(make<TextDocumentLine>(document()));
1127 m_selection.clear();
1128 did_update_selection();
1129 set_cursor(0, 0);
1130 update();
1131}
1132
1133String TextEditor::selected_text() const
1134{
1135 if (!has_selection())
1136 return {};
1137
1138 return document().text_in_range(m_selection);
1139}
1140
1141void TextEditor::delete_selection()
1142{
1143 auto selection = normalized_selection();
1144 execute<RemoveTextCommand>(selected_text(), selection);
1145 m_selection.clear();
1146 did_update_selection();
1147 did_change();
1148 set_cursor(selection.start());
1149 update();
1150}
1151
1152void TextEditor::insert_at_cursor_or_replace_selection(const StringView& text)
1153{
1154 ASSERT(!is_readonly());
1155 if (has_selection())
1156 delete_selection();
1157 execute<InsertTextCommand>(text, m_cursor);
1158}
1159
1160void TextEditor::cut()
1161{
1162 if (is_readonly())
1163 return;
1164 auto selected_text = this->selected_text();
1165 printf("Cut: \"%s\"\n", selected_text.characters());
1166 Clipboard::the().set_data(selected_text);
1167 delete_selection();
1168}
1169
1170void TextEditor::copy()
1171{
1172 auto selected_text = this->selected_text();
1173 printf("Copy: \"%s\"\n", selected_text.characters());
1174 Clipboard::the().set_data(selected_text);
1175}
1176
1177void TextEditor::paste()
1178{
1179 if (is_readonly())
1180 return;
1181 auto paste_text = Clipboard::the().data();
1182 printf("Paste: \"%s\"\n", paste_text.characters());
1183
1184 TemporaryChange change(m_automatic_indentation_enabled, false);
1185 insert_at_cursor_or_replace_selection(paste_text);
1186}
1187
1188void TextEditor::enter_event(Core::Event&)
1189{
1190 ASSERT(window());
1191 window()->set_override_cursor(StandardCursor::IBeam);
1192}
1193
1194void TextEditor::leave_event(Core::Event&)
1195{
1196 ASSERT(window());
1197 window()->set_override_cursor(StandardCursor::None);
1198}
1199
1200void TextEditor::did_change()
1201{
1202 update_content_size();
1203 recompute_all_visual_lines();
1204 m_undo_action->set_enabled(can_undo());
1205 m_redo_action->set_enabled(can_redo());
1206 if (!m_has_pending_change_notification) {
1207 m_has_pending_change_notification = true;
1208 deferred_invoke([this](auto&) {
1209 if (!m_has_pending_change_notification)
1210 return;
1211 if (on_change)
1212 on_change();
1213 if (m_highlighter)
1214 m_highlighter->rehighlight(palette());
1215 m_has_pending_change_notification = false;
1216 });
1217 }
1218}
1219
1220void TextEditor::set_readonly(bool readonly)
1221{
1222 if (m_readonly == readonly)
1223 return;
1224 m_readonly = readonly;
1225 m_cut_action->set_enabled(!is_readonly() && has_selection());
1226 m_delete_action->set_enabled(!is_readonly());
1227 m_paste_action->set_enabled(!is_readonly());
1228}
1229
1230void TextEditor::did_update_selection()
1231{
1232 m_cut_action->set_enabled(!is_readonly() && has_selection());
1233 m_copy_action->set_enabled(has_selection());
1234 if (on_selection_change)
1235 on_selection_change();
1236 if (is_line_wrapping_enabled()) {
1237 // FIXME: Try to repaint less.
1238 update();
1239 }
1240}
1241
1242void TextEditor::context_menu_event(ContextMenuEvent& event)
1243{
1244 if (!m_context_menu) {
1245 m_context_menu = Menu::construct();
1246 m_context_menu->add_action(undo_action());
1247 m_context_menu->add_action(redo_action());
1248 m_context_menu->add_separator();
1249 m_context_menu->add_action(cut_action());
1250 m_context_menu->add_action(copy_action());
1251 m_context_menu->add_action(paste_action());
1252 m_context_menu->add_action(delete_action());
1253 if (is_multi_line()) {
1254 m_context_menu->add_separator();
1255 m_context_menu->add_action(go_to_line_action());
1256 }
1257 if (!m_custom_context_menu_actions.is_empty()) {
1258 m_context_menu->add_separator();
1259 for (auto& action : m_custom_context_menu_actions) {
1260 m_context_menu->add_action(action);
1261 }
1262 }
1263 }
1264 m_context_menu->popup(event.screen_position());
1265}
1266
1267void TextEditor::set_text_alignment(Gfx::TextAlignment alignment)
1268{
1269 if (m_text_alignment == alignment)
1270 return;
1271 m_text_alignment = alignment;
1272 update();
1273}
1274
1275void TextEditor::resize_event(ResizeEvent& event)
1276{
1277 ScrollableWidget::resize_event(event);
1278 update_content_size();
1279 recompute_all_visual_lines();
1280}
1281
1282void TextEditor::theme_change_event(ThemeChangeEvent& event)
1283{
1284 ScrollableWidget::theme_change_event(event);
1285 if (m_highlighter)
1286 m_highlighter->rehighlight(palette());
1287}
1288
1289void TextEditor::set_selection(const TextRange& selection)
1290{
1291 if (m_selection == selection)
1292 return;
1293 m_selection = selection;
1294 set_cursor(m_selection.end());
1295 scroll_position_into_view(normalized_selection().start());
1296 update();
1297}
1298
1299void TextEditor::clear_selection()
1300{
1301 if (!has_selection())
1302 return;
1303 m_selection.clear();
1304 update();
1305}
1306
1307void TextEditor::recompute_all_visual_lines()
1308{
1309 int y_offset = 0;
1310 for (size_t line_index = 0; line_index < line_count(); ++line_index) {
1311 recompute_visual_lines(line_index);
1312 m_line_visual_data[line_index].visual_rect.set_y(y_offset);
1313 y_offset += m_line_visual_data[line_index].visual_rect.height();
1314 }
1315
1316 update_content_size();
1317}
1318
1319void TextEditor::ensure_cursor_is_valid()
1320{
1321 auto new_cursor = m_cursor;
1322 if (new_cursor.line() >= line_count())
1323 new_cursor.set_line(line_count() - 1);
1324 if (new_cursor.column() > line(new_cursor.line()).length())
1325 new_cursor.set_column(line(new_cursor.line()).length());
1326 if (m_cursor != new_cursor)
1327 set_cursor(new_cursor);
1328}
1329
1330size_t TextEditor::visual_line_containing(size_t line_index, size_t column) const
1331{
1332 size_t visual_line_index = 0;
1333 for_each_visual_line(line_index, [&](const Gfx::Rect&, const StringView& view, size_t start_of_visual_line) {
1334 if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
1335 return IterationDecision::Break;
1336 ++visual_line_index;
1337 return IterationDecision::Continue;
1338 });
1339 return visual_line_index;
1340}
1341
1342void TextEditor::recompute_visual_lines(size_t line_index)
1343{
1344 auto& line = document().line(line_index);
1345 auto& visual_data = m_line_visual_data[line_index];
1346
1347 visual_data.visual_line_breaks.clear_with_capacity();
1348
1349 int available_width = visible_text_rect_in_inner_coordinates().width();
1350
1351 if (is_line_wrapping_enabled()) {
1352 int line_width_so_far = 0;
1353
1354 for (size_t i = 0; i < line.length(); ++i) {
1355 auto ch = line.characters()[i];
1356 auto glyph_width = font().glyph_width(ch);
1357 if ((line_width_so_far + glyph_width) > available_width) {
1358 visual_data.visual_line_breaks.append(i);
1359 line_width_so_far = glyph_width;
1360 continue;
1361 }
1362 line_width_so_far += glyph_width;
1363 }
1364 }
1365
1366 visual_data.visual_line_breaks.append(line.length());
1367
1368 if (is_line_wrapping_enabled())
1369 visual_data.visual_rect = { m_horizontal_content_padding, 0, available_width, static_cast<int>(visual_data.visual_line_breaks.size()) * line_height() };
1370 else
1371 visual_data.visual_rect = { m_horizontal_content_padding, 0, font().width(line.view()), line_height() };
1372}
1373
1374template<typename Callback>
1375void TextEditor::for_each_visual_line(size_t line_index, Callback callback) const
1376{
1377 auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates();
1378 size_t start_of_line = 0;
1379 size_t visual_line_index = 0;
1380
1381 auto& line = document().line(line_index);
1382 auto& visual_data = m_line_visual_data[line_index];
1383
1384 for (auto visual_line_break : visual_data.visual_line_breaks) {
1385 auto visual_line_view = StringView(line.characters() + start_of_line, visual_line_break - start_of_line);
1386 Gfx::Rect visual_line_rect {
1387 visual_data.visual_rect.x(),
1388 visual_data.visual_rect.y() + ((int)visual_line_index * line_height()),
1389 font().width(visual_line_view),
1390 line_height()
1391 };
1392 if (is_right_text_alignment(text_alignment()))
1393 visual_line_rect.set_right_without_resize(editor_visible_text_rect.right());
1394 if (!is_multi_line())
1395 visual_line_rect.center_vertically_within(editor_visible_text_rect);
1396 if (callback(visual_line_rect, visual_line_view, start_of_line) == IterationDecision::Break)
1397 break;
1398 start_of_line = visual_line_break;
1399 ++visual_line_index;
1400 }
1401}
1402
1403void TextEditor::set_line_wrapping_enabled(bool enabled)
1404{
1405 if (m_line_wrapping_enabled == enabled)
1406 return;
1407
1408 m_line_wrapping_enabled = enabled;
1409 horizontal_scrollbar().set_visible(!m_line_wrapping_enabled);
1410 update_content_size();
1411 recompute_all_visual_lines();
1412 update();
1413}
1414
1415void TextEditor::add_custom_context_menu_action(Action& action)
1416{
1417 m_custom_context_menu_actions.append(action);
1418}
1419
1420void TextEditor::did_change_font()
1421{
1422 vertical_scrollbar().set_step(line_height());
1423 recompute_all_visual_lines();
1424 update();
1425 Widget::did_change_font();
1426}
1427
1428void TextEditor::document_did_append_line()
1429{
1430 m_line_visual_data.append(make<LineVisualData>());
1431 recompute_all_visual_lines();
1432 update();
1433}
1434
1435void TextEditor::document_did_remove_line(size_t line_index)
1436{
1437 m_line_visual_data.remove(line_index);
1438 recompute_all_visual_lines();
1439 update();
1440}
1441
1442void TextEditor::document_did_remove_all_lines()
1443{
1444 m_line_visual_data.clear();
1445 recompute_all_visual_lines();
1446 update();
1447}
1448
1449void TextEditor::document_did_insert_line(size_t line_index)
1450{
1451 m_line_visual_data.insert(line_index, make<LineVisualData>());
1452 recompute_all_visual_lines();
1453 update();
1454}
1455
1456void TextEditor::document_did_change()
1457{
1458 did_change();
1459 update();
1460}
1461
1462void TextEditor::document_did_set_text()
1463{
1464 m_line_visual_data.clear();
1465 for (size_t i = 0; i < m_document->line_count(); ++i)
1466 m_line_visual_data.append(make<LineVisualData>());
1467 document_did_change();
1468}
1469
1470void TextEditor::document_did_set_cursor(const TextPosition& position)
1471{
1472 set_cursor(position);
1473}
1474
1475void TextEditor::set_document(TextDocument& document)
1476{
1477 if (m_document.ptr() == &document)
1478 return;
1479 if (m_document)
1480 m_document->unregister_client(*this);
1481 m_document = document;
1482 m_line_visual_data.clear();
1483 for (size_t i = 0; i < m_document->line_count(); ++i) {
1484 m_line_visual_data.append(make<LineVisualData>());
1485 }
1486 m_cursor = { 0, 0 };
1487 if (has_selection())
1488 m_selection.clear();
1489 recompute_all_visual_lines();
1490 update();
1491 m_document->register_client(*this);
1492}
1493
1494void TextEditor::flush_pending_change_notification_if_needed()
1495{
1496 if (!m_has_pending_change_notification)
1497 return;
1498 if (on_change)
1499 on_change();
1500 if (m_highlighter)
1501 m_highlighter->rehighlight(palette());
1502 m_has_pending_change_notification = false;
1503}
1504
1505const SyntaxHighlighter* TextEditor::syntax_highlighter() const
1506{
1507 return m_highlighter.ptr();
1508}
1509
1510void TextEditor::set_syntax_highlighter(OwnPtr<SyntaxHighlighter> highlighter)
1511{
1512 if (m_highlighter)
1513 m_highlighter->detach();
1514 m_highlighter = move(highlighter);
1515 if (m_highlighter) {
1516 m_highlighter->attach(*this);
1517 m_highlighter->rehighlight(palette());
1518 } else
1519 document().set_spans({});
1520}
1521
1522int TextEditor::line_height() const
1523{
1524 return font().glyph_height() + m_line_spacing;
1525}
1526
1527int TextEditor::glyph_width() const
1528{
1529 return font().glyph_width('x');
1530}
1531
1532}