Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, networkException <networkexception@serenityos.org>
4 * Copyright (c) 2022, the SerenityOS developers.
5 * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
6 *
7 * SPDX-License-Identifier: BSD-2-Clause
8 */
9
10#include <AK/CharacterTypes.h>
11#include <AK/Debug.h>
12#include <AK/ScopeGuard.h>
13#include <AK/StringBuilder.h>
14#include <AK/TemporaryChange.h>
15#include <LibCore/DeprecatedFile.h>
16#include <LibCore/Timer.h>
17#include <LibGUI/Action.h>
18#include <LibGUI/AutocompleteProvider.h>
19#include <LibGUI/Clipboard.h>
20#include <LibGUI/EditingEngine.h>
21#include <LibGUI/EmojiInputDialog.h>
22#include <LibGUI/IncrementalSearchBanner.h>
23#include <LibGUI/InputBox.h>
24#include <LibGUI/Menu.h>
25#include <LibGUI/Painter.h>
26#include <LibGUI/RegularEditingEngine.h>
27#include <LibGUI/Scrollbar.h>
28#include <LibGUI/TextEditor.h>
29#include <LibGUI/Window.h>
30#include <LibGfx/Bitmap.h>
31#include <LibGfx/Font/Font.h>
32#include <LibGfx/Font/FontDatabase.h>
33#include <LibGfx/Palette.h>
34#include <LibGfx/StandardCursor.h>
35#include <LibSyntax/Highlighter.h>
36#include <LibUnicode/Segmentation.h>
37#include <fcntl.h>
38#include <stdio.h>
39#include <unistd.h>
40
41REGISTER_WIDGET(GUI, TextEditor)
42
43namespace GUI {
44
45static constexpr StringView folded_region_summary_text = " ..."sv;
46
47TextEditor::TextEditor(Type type)
48 : m_type(type)
49{
50 REGISTER_STRING_PROPERTY("text", text, set_text);
51 REGISTER_STRING_PROPERTY("placeholder", placeholder, set_placeholder);
52 REGISTER_BOOL_PROPERTY("gutter", is_gutter_visible, set_gutter_visible);
53 REGISTER_BOOL_PROPERTY("ruler", is_ruler_visible, set_ruler_visible);
54 REGISTER_ENUM_PROPERTY("mode", mode, set_mode, Mode,
55 { Editable, "Editable" },
56 { ReadOnly, "ReadOnly" },
57 { DisplayOnly, "DisplayOnly" });
58
59 set_focus_policy(GUI::FocusPolicy::StrongFocus);
60 set_or_clear_emoji_input_callback();
61 set_override_cursor(Gfx::StandardCursor::IBeam);
62 set_background_role(ColorRole::Base);
63 set_foreground_role(ColorRole::BaseText);
64 set_document(TextDocument::create());
65 if (is_single_line())
66 set_visualize_trailing_whitespace(false);
67 set_scrollbars_enabled(is_multi_line());
68 if (is_multi_line()) {
69 set_font(Gfx::FontDatabase::default_fixed_width_font());
70 set_wrapping_mode(WrappingMode::WrapAtWords);
71 m_search_banner = GUI::IncrementalSearchBanner::construct(*this);
72 set_banner_widget(m_search_banner);
73 }
74 vertical_scrollbar().set_step(line_height());
75 m_cursor = { 0, 0 };
76 create_actions();
77 set_editing_engine(make<RegularEditingEngine>());
78}
79
80TextEditor::~TextEditor()
81{
82 if (m_document)
83 m_document->unregister_client(*this);
84}
85
86void TextEditor::create_actions()
87{
88 m_undo_action = CommonActions::make_undo_action([&](auto&) { undo(); }, this);
89 m_redo_action = CommonActions::make_redo_action([&](auto&) { redo(); }, this);
90 m_undo_action->set_enabled(false);
91 m_redo_action->set_enabled(false);
92 m_cut_action = CommonActions::make_cut_action([&](auto&) { cut(); }, this);
93 m_copy_action = CommonActions::make_copy_action([&](auto&) { copy(); }, this);
94 m_cut_action->set_enabled(false);
95 m_copy_action->set_enabled(false);
96 m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this);
97 m_paste_action->set_enabled(is_editable() && Clipboard::the().fetch_mime_type().starts_with("text/"sv));
98 if (is_multi_line()) {
99 m_go_to_line_action = Action::create(
100 "Go to line...", { Mod_Ctrl, Key_L }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-to.png"sv).release_value_but_fixme_should_propagate_errors(), [this](auto&) {
101 DeprecatedString value;
102 if (InputBox::show(window(), value, "Line:"sv, "Go to line"sv) == InputBox::ExecResult::OK) {
103 auto line_target = value.to_uint();
104 if (line_target.has_value()) {
105 set_cursor_and_focus_line(line_target.value() - 1, 0);
106 }
107 }
108 },
109 this);
110 }
111 m_select_all_action = CommonActions::make_select_all_action([this](auto&) { select_all(); }, this);
112 m_insert_emoji_action = CommonActions::make_insert_emoji_action([&](auto&) { insert_emoji(); }, this);
113}
114
115void TextEditor::set_text(StringView text, AllowCallback allow_callback)
116{
117 m_selection.clear();
118
119 document().set_text(text, allow_callback);
120
121 update_content_size();
122 recompute_all_visual_lines();
123 if (is_single_line())
124 set_cursor(0, line(0).length());
125 else
126 set_cursor(0, 0);
127 did_update_selection();
128 update();
129}
130
131void TextEditor::update_content_size()
132{
133 int content_width = 0;
134 int content_height = 0;
135 for (auto& line : m_line_visual_data) {
136 content_width = max(line->visual_rect.width(), content_width);
137 content_height += line->visual_rect.height();
138 }
139 content_width += m_horizontal_content_padding * 2;
140 if (is_right_text_alignment(m_text_alignment))
141 content_width = max(frame_inner_rect().width(), content_width);
142
143 set_content_size({ content_width, content_height });
144 set_size_occupied_by_fixed_elements({ fixed_elements_width(), 0 });
145}
146
147TextPosition TextEditor::text_position_at_content_position(Gfx::IntPoint content_position) const
148{
149 auto position = content_position;
150 if (is_single_line() && icon())
151 position.translate_by(-(icon_size() + icon_padding()), 0);
152
153 size_t line_index = 0;
154
155 if (position.y() >= 0) {
156 size_t last_visible_line_index = 0;
157 // FIXME: Oh boy is this a slow way of calculating this!
158 // NOTE: Offset by 1 in calculations is because we can't do `i >= 0` with an unsigned type.
159 for (size_t i = line_count(); i > 0; --i) {
160 if (document().line_is_visible(i - 1)) {
161 last_visible_line_index = i - 1;
162 break;
163 }
164 }
165
166 for (size_t i = 0; i < line_count(); ++i) {
167 if (!document().line_is_visible(i))
168 continue;
169
170 auto& rect = m_line_visual_data[i]->visual_rect;
171 if (position.y() >= rect.top() && position.y() <= rect.bottom()) {
172 line_index = i;
173 break;
174 }
175 if (position.y() > rect.bottom())
176 line_index = last_visible_line_index;
177 }
178 line_index = max((size_t)0, min(line_index, last_visible_line_index));
179 }
180
181 size_t column_index = 0;
182 switch (m_text_alignment) {
183 case Gfx::TextAlignment::CenterLeft:
184 for_each_visual_line(line_index, [&](Gfx::IntRect const& rect, auto& view, size_t start_of_line, [[maybe_unused]] bool is_last_visual_line) {
185 if (is_multi_line() && !rect.contains_vertically(position.y()) && !is_last_visual_line)
186 return IterationDecision::Continue;
187
188 column_index = start_of_line;
189 int glyph_x = 0;
190
191 if (position.x() <= 0) {
192 // We're outside the text on the left side, put cursor at column 0 on this visual line.
193 return IterationDecision::Break;
194 }
195
196 for (auto it = view.begin(); it != view.end(); ++it, ++column_index) {
197 int advance = font().glyph_or_emoji_width(it) + font().glyph_spacing();
198 if ((glyph_x + (advance / 2)) >= position.x())
199 break;
200
201 glyph_x += advance;
202 }
203
204 return IterationDecision::Break;
205 });
206 break;
207 case Gfx::TextAlignment::CenterRight:
208 // FIXME: Support right-aligned line wrapping, I guess.
209 VERIFY(!is_wrapping_enabled());
210 column_index = (position.x() - content_x_for_position({ line_index, 0 }) + fixed_glyph_width() / 2) / fixed_glyph_width();
211 break;
212 default:
213 VERIFY_NOT_REACHED();
214 }
215
216 column_index = max((size_t)0, min(column_index, line(line_index).length()));
217 return { line_index, column_index };
218}
219
220TextPosition TextEditor::text_position_at(Gfx::IntPoint widget_position) const
221{
222 auto content_position = widget_position;
223 content_position.translate_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
224 content_position.translate_by(-(m_horizontal_content_padding + fixed_elements_width()), 0);
225 content_position.translate_by(-frame_thickness(), -frame_thickness());
226 content_position.translate_by(0, -height_occupied_by_banner_widget());
227 return text_position_at_content_position(content_position);
228}
229
230void TextEditor::doubleclick_event(MouseEvent& event)
231{
232 if (event.button() != MouseButton::Primary)
233 return;
234
235 if (is_displayonly())
236 return;
237
238 if (!current_line().can_select())
239 return;
240
241 rehighlight_if_needed();
242
243 m_triple_click_timer.start();
244 m_in_drag_select = false;
245
246 auto position = text_position_at(event.position());
247
248 if (m_substitution_code_point.has_value()) {
249 // NOTE: If we substitute the code points, we don't want double clicking to only select a single word, since
250 // whitespace isn't visible anymore.
251 m_selection = document().range_for_entire_line(position.line());
252 } else if (document().has_spans()) {
253 for (auto& span : document().spans()) {
254 if (span.range.contains(position)) {
255 m_selection = span.range;
256 break;
257 }
258 }
259 } else {
260 m_selection.set_start(document().first_word_break_before(position, false));
261 m_selection.set_end(document().first_word_break_after(position));
262 }
263
264 set_cursor(m_selection.end());
265 update();
266 did_update_selection();
267}
268
269void TextEditor::mousedown_event(MouseEvent& event)
270{
271 using namespace AK::TimeLiterals;
272
273 if (event.button() != MouseButton::Primary) {
274 return;
275 }
276
277 auto text_position = text_position_at(event.position());
278 if (event.modifiers() == 0 && folding_indicator_rect(text_position.line()).contains(event.position())) {
279 if (auto folding_region = document().folding_region_starting_on_line(text_position.line()); folding_region.has_value()) {
280 folding_region->is_folded = !folding_region->is_folded;
281 dbgln_if(TEXTEDITOR_DEBUG, "TextEditor: {} region {}.", folding_region->is_folded ? "Folding"sv : "Unfolding"sv, folding_region->range);
282
283 if (folding_region->is_folded && folding_region->range.contains(cursor())) {
284 // Cursor is now within a hidden range, so move it outside.
285 set_cursor(folding_region->range.start());
286 }
287
288 recompute_all_visual_lines();
289 update();
290 return;
291 }
292 }
293
294 if (on_mousedown)
295 on_mousedown();
296
297 if (is_displayonly())
298 return;
299
300 if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed_time() < 250_ms) {
301 m_triple_click_timer = Core::ElapsedTimer();
302 select_current_line();
303 return;
304 }
305
306 if (event.modifiers() & Mod_Shift) {
307 if (!has_selection())
308 m_selection.set(m_cursor, {});
309 } else {
310 m_selection.clear();
311 }
312
313 m_in_drag_select = true;
314
315 set_cursor_to_text_position(event.position());
316
317 if (!(event.modifiers() & Mod_Shift)) {
318 if (!has_selection())
319 m_selection.set(m_cursor, {});
320 }
321
322 if (m_selection.start().is_valid() && m_selection.start() != m_cursor)
323 m_selection.set_end(m_cursor);
324
325 // FIXME: Only update the relevant rects.
326 update();
327 did_update_selection();
328}
329
330void TextEditor::mouseup_event(MouseEvent& event)
331{
332 if (event.button() == MouseButton::Primary) {
333 if (m_in_drag_select) {
334 m_in_drag_select = false;
335 }
336 return;
337 }
338}
339
340void TextEditor::mousemove_event(MouseEvent& event)
341{
342 m_last_mousemove_position = event.position();
343 if (m_in_drag_select) {
344 auto constrained = event.position().constrained(widget_inner_rect());
345 set_cursor_to_text_position(constrained);
346 m_selection.set_end(m_cursor);
347 did_update_selection();
348 update();
349 return;
350 }
351
352 if (m_ruler_visible && ruler_rect_in_inner_coordinates().contains(event.position())) {
353 set_override_cursor(Gfx::StandardCursor::None);
354 } else if (m_ruler_visible && folding_indicator_rect_in_inner_coordinates().contains(event.position())) {
355 auto text_position = text_position_at(event.position());
356 if (document().folding_region_starting_on_line(text_position.line()).has_value()
357 && folding_indicator_rect(text_position.line()).contains(event.position())) {
358 set_override_cursor(Gfx::StandardCursor::Hand);
359 } else {
360 set_override_cursor(Gfx::StandardCursor::None);
361 }
362 } else if (m_gutter_visible && gutter_rect_in_inner_coordinates().contains(event.position())) {
363 set_override_cursor(Gfx::StandardCursor::None);
364 } else {
365 set_editing_cursor();
366 }
367}
368
369void TextEditor::select_current_line()
370{
371 m_selection = document().range_for_entire_line(m_cursor.line());
372 set_cursor(m_selection.end());
373 update();
374 did_update_selection();
375}
376
377void TextEditor::automatic_scrolling_timer_did_fire()
378{
379 if (!m_in_drag_select) {
380 set_automatic_scrolling_timer_active(false);
381 return;
382 }
383 set_cursor_to_text_position(m_last_mousemove_position);
384 m_selection.set_end(m_cursor);
385 did_update_selection();
386 update();
387}
388
389int TextEditor::folding_indicator_width() const
390{
391 return document().has_folding_regions() ? line_height() : 0;
392}
393
394int TextEditor::ruler_width() const
395{
396 if (!m_ruler_visible)
397 return 0;
398 int line_count_digits = static_cast<int>(log10(line_count())) + 1;
399 auto padding = 5 + (font().is_fixed_width() ? 1 : (line_count_digits - (line_count() < 10 ? -1 : 0)));
400 auto widest_numeral = font().bold_variant().glyph_width('4');
401 return line_count() < 10 ? (line_count_digits + 1) * widest_numeral + padding : line_count_digits * widest_numeral + padding;
402}
403
404int TextEditor::gutter_width() const
405{
406 if (!m_gutter_visible)
407 return 0;
408 return line_height(); // square gutter
409}
410
411Gfx::IntRect TextEditor::gutter_content_rect(size_t line_index) const
412{
413 if (!m_gutter_visible)
414 return {};
415
416 return {
417 0,
418 line_content_rect(line_index).y() - vertical_scrollbar().value(),
419 gutter_width(),
420 line_content_rect(line_index).height()
421 };
422}
423
424Gfx::IntRect TextEditor::ruler_content_rect(size_t line_index) const
425{
426 if (!m_ruler_visible)
427 return {};
428
429 return {
430 gutter_width(),
431 line_content_rect(line_index).y() - vertical_scrollbar().value(),
432 ruler_width(),
433 line_content_rect(line_index).height()
434 };
435}
436
437Gfx::IntRect TextEditor::folding_indicator_rect(size_t line_index) const
438{
439 if (!m_ruler_visible || !document().has_folding_regions())
440 return {};
441
442 return {
443 gutter_width() + ruler_width(),
444 line_content_rect(line_index).y() - vertical_scrollbar().value(),
445 folding_indicator_width(),
446 line_content_rect(line_index).height()
447 };
448}
449
450Gfx::IntRect TextEditor::gutter_rect_in_inner_coordinates() const
451{
452 return { 0, 0, gutter_width(), widget_inner_rect().height() };
453}
454
455Gfx::IntRect TextEditor::ruler_rect_in_inner_coordinates() const
456{
457 return { gutter_width(), 0, ruler_width(), widget_inner_rect().height() };
458}
459
460Gfx::IntRect TextEditor::folding_indicator_rect_in_inner_coordinates() const
461{
462 return { gutter_width() + ruler_width(), 0, folding_indicator_width(), widget_inner_rect().height() };
463}
464
465Gfx::IntRect TextEditor::visible_text_rect_in_inner_coordinates() const
466{
467 return {
468 m_horizontal_content_padding + fixed_elements_width(),
469 0,
470 frame_inner_rect().width() - (m_horizontal_content_padding * 2) - width_occupied_by_vertical_scrollbar() - fixed_elements_width(),
471 frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar()
472 };
473}
474
475void TextEditor::paint_event(PaintEvent& event)
476{
477 Color widget_background_color = palette().color(is_enabled() ? background_role() : Gfx::ColorRole::Window);
478
479 rehighlight_if_needed();
480
481 Frame::paint_event(event);
482
483 Painter painter(*this);
484 painter.add_clip_rect(widget_inner_rect());
485 painter.add_clip_rect(event.rect());
486 painter.fill_rect(event.rect(), widget_background_color);
487
488 Gfx::TextAttributes unspanned_text_attributes;
489 if (is_displayonly() && is_focused()) {
490 unspanned_text_attributes.color = palette().color(is_enabled() ? Gfx::ColorRole::SelectionText : Gfx::ColorRole::DisabledText);
491 } else {
492 unspanned_text_attributes.color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText);
493 }
494
495 auto& folded_region_summary_font = font().bold_variant();
496 Gfx::TextAttributes folded_region_summary_attributes { palette().color(Gfx::ColorRole::SyntaxComment) };
497
498 // NOTE: This lambda and TextEditor::text_width_for_font() are used to substitute all glyphs with m_substitution_code_point if necessary.
499 // Painter::draw_text() and Gfx::Font::width() should not be called directly, but using this lambda and TextEditor::text_width_for_font().
500 auto draw_text = [&](auto const& rect, auto const& raw_text, Gfx::Font const& font, Gfx::TextAlignment alignment, Gfx::TextAttributes attributes, bool substitute = true) {
501 if (m_substitution_code_point.has_value() && substitute) {
502 painter.draw_text(rect, substitution_code_point_view(raw_text.length()), font, alignment, attributes.color);
503 } else {
504 painter.draw_text(rect, raw_text, font, alignment, attributes.color);
505 }
506 if (attributes.underline) {
507 auto bottom_left = [&]() {
508 auto point = rect.bottom_left().translated(0, 1);
509
510 if constexpr (IsSame<RemoveCVReference<decltype(rect)>, Gfx::IntRect>)
511 return point;
512 else
513 return point.template to_type<int>();
514 };
515
516 auto bottom_right = [&]() {
517 auto point = rect.bottom_right().translated(0, 1);
518
519 if constexpr (IsSame<RemoveCVReference<decltype(rect)>, Gfx::IntRect>)
520 return point;
521 else
522 return point.template to_type<int>();
523 };
524
525 if (attributes.underline_style == Gfx::TextAttributes::UnderlineStyle::Solid)
526 painter.draw_line(bottom_left(), bottom_right(), attributes.underline_color.value_or(attributes.color));
527 if (attributes.underline_style == Gfx::TextAttributes::UnderlineStyle::Wavy)
528 painter.draw_triangle_wave(bottom_left(), bottom_right(), attributes.underline_color.value_or(attributes.color), 2);
529 }
530 };
531
532 if (is_displayonly() && is_focused()) {
533 Gfx::IntRect display_rect {
534 widget_inner_rect().x() + 1,
535 widget_inner_rect().y() + 1,
536 widget_inner_rect().width() - 2,
537 widget_inner_rect().height() - 2
538 };
539 painter.fill_rect(display_rect, palette().selection());
540 }
541
542 painter.translate(frame_thickness(), frame_thickness());
543 painter.translate(0, height_occupied_by_banner_widget());
544
545 if (!is_multi_line() && m_icon) {
546 Gfx::IntRect icon_rect { icon_padding(), 1, icon_size(), icon_size() };
547 painter.draw_scaled_bitmap(icon_rect, *m_icon, m_icon->rect());
548 }
549
550 if (m_gutter_visible) {
551 auto gutter_rect = gutter_rect_in_inner_coordinates();
552 painter.fill_rect(gutter_rect, palette().gutter());
553 if (!m_ruler_visible)
554 painter.draw_line(gutter_rect.top_right(), gutter_rect.bottom_right(), palette().gutter_border());
555 }
556
557 if (m_ruler_visible) {
558 auto ruler_rect = ruler_rect_in_inner_coordinates().inflated(0, folding_indicator_width(), 0, 0);
559 painter.fill_rect(ruler_rect, palette().ruler());
560 painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), palette().ruler_border());
561
562 // Paint +/- buttons for folding regions
563 for (auto const& folding_region : document().folding_regions()) {
564 auto start_line = folding_region.range.start().line();
565 if (!document().line_is_visible(start_line))
566 continue;
567 auto fold_indicator_rect = folding_indicator_rect(start_line).shrunken(4, 4);
568 fold_indicator_rect.set_height(fold_indicator_rect.width());
569 painter.draw_rect(fold_indicator_rect, palette().ruler_inactive_text());
570 auto fold_symbol = folding_region.is_folded ? "+"sv : "-"sv;
571 painter.draw_text(fold_indicator_rect, fold_symbol, font(), Gfx::TextAlignment::Center, palette().ruler_inactive_text());
572 }
573 }
574
575 size_t first_visible_line = text_position_at(event.rect().top_left()).line();
576 size_t last_visible_line = text_position_at(event.rect().bottom_right()).line();
577
578 auto selection = normalized_selection();
579 bool has_selection = selection.is_valid();
580
581 if (m_ruler_visible) {
582 for (size_t i = first_visible_line; i <= last_visible_line; ++i) {
583 if (!document().line_is_visible(i))
584 continue;
585
586 bool is_current_line = i == m_cursor.line();
587 auto ruler_line_rect = ruler_content_rect(i);
588 // NOTE: Shrink the rectangle to be only on the first visual line.
589 auto const line_height = font().preferred_line_height();
590 if (ruler_line_rect.height() > line_height)
591 ruler_line_rect.set_height(line_height);
592 // NOTE: Use Painter::draw_text() directly here, as we want to always draw the line numbers in clear text.
593 size_t const line_number = is_relative_line_number() && !is_current_line ? max(i, m_cursor.line()) - min(i, m_cursor.line()) : i + 1;
594 painter.draw_text(
595 ruler_line_rect.shrunken(2, 0),
596 DeprecatedString::number(line_number),
597 is_current_line ? font().bold_variant() : font(),
598 Gfx::TextAlignment::CenterRight,
599 is_current_line ? palette().ruler_active_text() : palette().ruler_inactive_text());
600 }
601 }
602
603 auto horizontal_scrollbar_value = horizontal_scrollbar().value();
604 painter.translate(-horizontal_scrollbar_value, -vertical_scrollbar().value());
605 if (m_icon && horizontal_scrollbar_value > 0)
606 painter.translate(min(icon_size() + icon_padding(), horizontal_scrollbar_value), 0);
607
608 auto gutter_ruler_width = fixed_elements_width();
609 painter.translate(gutter_ruler_width, 0);
610 Gfx::IntRect text_clip_rect { 0, 0, widget_inner_rect().width() - gutter_ruler_width, widget_inner_rect().height() };
611 text_clip_rect.translate_by(horizontal_scrollbar().value(), vertical_scrollbar().value());
612 painter.add_clip_rect(text_clip_rect);
613
614 size_t span_index = 0;
615 if (document().has_spans()) {
616 for (;;) {
617 if (span_index >= document().spans().size() || document().spans()[span_index].range.end().line() >= first_visible_line) {
618 break;
619 }
620 ++span_index;
621 }
622 }
623
624 for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) {
625 auto& line = this->line(line_index);
626
627 bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line();
628 size_t first_visual_line_with_selection = 0;
629 size_t last_visual_line_with_selection = 0;
630 if (physical_line_has_selection) {
631 if (selection.start().line() < line_index)
632 first_visual_line_with_selection = 0;
633 else
634 first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column());
635
636 if (selection.end().line() > line_index)
637 last_visual_line_with_selection = m_line_visual_data[line_index]->visual_lines.size();
638 else
639 last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column());
640 }
641
642 size_t selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0;
643 size_t selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length();
644
645 size_t visual_line_index = 0;
646 size_t last_start_of_visual_line = 0;
647 Optional<size_t> multiline_trailing_space_offset {};
648 for_each_visual_line(line_index, [&](Gfx::IntRect const& visual_line_rect, auto& visual_line_text, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
649 ScopeGuard update_last_start_of_visual_line { [&]() { last_start_of_visual_line = start_of_visual_line; } };
650
651 if (is_focused() && is_multi_line() && line_index == m_cursor.line() && is_cursor_line_highlighted()) {
652 Gfx::IntRect visible_content_line_rect {
653 visible_content_rect().x(),
654 visual_line_rect.y(),
655 widget_inner_rect().width() - gutter_ruler_width,
656 line_height()
657 };
658 painter.fill_rect(visible_content_line_rect, widget_background_color.darkened(0.9f));
659 }
660 if constexpr (TEXTEDITOR_DEBUG)
661 painter.draw_rect(visual_line_rect, Color::Cyan);
662
663 if (!placeholder().is_empty() && document().is_empty() && line_index == 0) {
664 auto line_rect = visual_line_rect;
665 line_rect.set_width(text_width_for_font(placeholder(), font()));
666 draw_text(line_rect, placeholder(), font(), m_text_alignment, { palette().color(Gfx::ColorRole::PlaceholderText) }, false);
667 } else if (!document().has_spans()) {
668 // Fast-path for plain text
669 draw_text(visual_line_rect, visual_line_text, font(), m_text_alignment, unspanned_text_attributes);
670 } else {
671 size_t next_column = 0;
672 Gfx::FloatRect span_rect = { visual_line_rect.location(), { 0, line_height() } };
673
674 auto draw_text_helper = [&](size_t start, size_t end, Gfx::Font const& font, Gfx::TextAttributes text_attributes) {
675 size_t length = end - start;
676 if (length == 0)
677 return;
678 auto text = visual_line_text.substring_view(start, length);
679 span_rect.set_width(font.width(text) + font.glyph_spacing());
680 if (text_attributes.background_color.has_value()) {
681 painter.fill_rect(span_rect.to_type<int>(), text_attributes.background_color.value());
682 }
683 draw_text(span_rect, text, font, m_text_alignment, text_attributes);
684 span_rect.translate_by(span_rect.width(), 0);
685 };
686
687 bool started_new_folded_region = false;
688 while (span_index < document().spans().size()) {
689 auto& span = document().spans()[span_index];
690 // Skip spans that have ended before this point.
691 // That is, for spans that are for lines inside a folded region.
692 if ((span.range.end().line() < line_index)
693 || (span.range.end().line() == line_index && span.range.end().column() <= start_of_visual_line)) {
694 ++span_index;
695 continue;
696 }
697 if (span.range.start().line() > line_index
698 || (span.range.start().line() == line_index && span.range.start().column() >= start_of_visual_line + visual_line_text.length())) {
699 // no more spans in this line, moving on
700 break;
701 }
702 size_t span_start;
703 if (span.range.start().line() < line_index || span.range.start().column() < start_of_visual_line) {
704 span_start = 0;
705 } else {
706 span_start = span.range.start().column() - start_of_visual_line;
707 }
708 size_t span_end;
709 bool span_consumed;
710 if (span.range.end().line() > line_index || span.range.end().column() > start_of_visual_line + visual_line_text.length()) {
711 span_end = visual_line_text.length();
712 span_consumed = false;
713 } else {
714 span_end = span.range.end().column() - start_of_visual_line;
715 span_consumed = true;
716 }
717
718 if (span_start != next_column) {
719 // draw unspanned text between spans
720 draw_text_helper(next_column, span_start, font(), unspanned_text_attributes);
721 }
722 auto& span_font = span.attributes.bold ? font().bold_variant() : font();
723 draw_text_helper(span_start, span_end, span_font, span.attributes);
724 next_column = span_end;
725 if (!span_consumed) {
726 // continue with same span on next line
727 break;
728 } else {
729 ++span_index;
730 }
731 }
732 // draw unspanned text after last span
733 if (!started_new_folded_region && next_column < visual_line_text.length()) {
734 draw_text_helper(next_column, visual_line_text.length(), font(), unspanned_text_attributes);
735 }
736
737 // Paint "..." at the end of the line if it starts a folded region.
738 // FIXME: This doesn't wrap.
739 if (is_last_visual_line) {
740 if (auto folded_region = document().folding_region_starting_on_line(line_index); folded_region.has_value() && folded_region->is_folded) {
741 span_rect.set_width(folded_region_summary_font.width(folded_region_summary_text));
742 draw_text(span_rect, folded_region_summary_text, folded_region_summary_font, m_text_alignment, folded_region_summary_attributes);
743 }
744 }
745 }
746
747 if (m_visualize_trailing_whitespace && line.ends_in_whitespace()) {
748 size_t physical_column;
749 auto last_non_whitespace_column = line.last_non_whitespace_column();
750 if (last_non_whitespace_column.has_value())
751 physical_column = last_non_whitespace_column.value() + 1;
752 else
753 physical_column = 0;
754 size_t end_of_visual_line = (start_of_visual_line + visual_line_text.length());
755 if (physical_column < end_of_visual_line) {
756 physical_column -= multiline_trailing_space_offset.value_or(0);
757
758 size_t visual_column = physical_column > start_of_visual_line ? (physical_column - start_of_visual_line) : 0;
759
760 Gfx::IntRect whitespace_rect {
761 content_x_for_position({ line_index, physical_column }),
762 visual_line_rect.y(),
763 text_width_for_font(visual_line_text.substring_view(visual_column, visual_line_text.length() - visual_column), font()),
764 visual_line_rect.height()
765 };
766 painter.fill_rect_with_dither_pattern(whitespace_rect, Color(), Color(255, 192, 192));
767
768 if (!multiline_trailing_space_offset.has_value())
769 multiline_trailing_space_offset = physical_column - last_start_of_visual_line;
770 }
771 }
772 if (m_visualize_leading_whitespace && line.leading_spaces() > 0) {
773 size_t physical_column = line.leading_spaces();
774 if (start_of_visual_line < physical_column) {
775 size_t end_of_leading_whitespace = min(physical_column - start_of_visual_line, visual_line_text.length());
776 Gfx::IntRect whitespace_rect {
777 content_x_for_position({ line_index, start_of_visual_line }),
778 visual_line_rect.y(),
779 text_width_for_font(visual_line_text.substring_view(0, end_of_leading_whitespace), font()),
780 visual_line_rect.height()
781 };
782 painter.fill_rect_with_dither_pattern(whitespace_rect, Color(), Color(192, 255, 192));
783 }
784 }
785
786 if (physical_line_has_selection && window()->focused_widget() == this) {
787 size_t const start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line);
788 size_t const end_of_selection_within_visual_line = min(selection_end_column_within_line - start_of_visual_line, visual_line_text.length());
789
790 bool current_visual_line_has_selection = start_of_selection_within_visual_line != end_of_selection_within_visual_line
791 && ((line_index != selection.start().line() && line_index != selection.end().line())
792 || (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection));
793 if (current_visual_line_has_selection) {
794 bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection;
795 bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection;
796
797 int selection_left = selection_begins_on_current_visual_line
798 ? content_x_for_position({ line_index, (size_t)selection_start_column_within_line })
799 : m_horizontal_content_padding;
800
801 int selection_right = selection_ends_on_current_visual_line
802 ? content_x_for_position({ line_index, (size_t)selection_end_column_within_line })
803 : visual_line_rect.right() + 1;
804
805 Gfx::IntRect selection_rect {
806 selection_left,
807 visual_line_rect.y(),
808 selection_right - selection_left,
809 visual_line_rect.height()
810 };
811
812 Color background_color = is_focused() ? palette().selection() : palette().inactive_selection();
813 Color text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text();
814
815 painter.fill_rect(selection_rect, background_color);
816
817 if (visual_line_text.code_points()) {
818 Utf32View visual_selected_text {
819 visual_line_text.code_points() + start_of_selection_within_visual_line,
820 end_of_selection_within_visual_line - start_of_selection_within_visual_line
821 };
822
823 draw_text(selection_rect, visual_selected_text, font(), Gfx::TextAlignment::CenterLeft, { text_color });
824 }
825 }
826 }
827
828 ++visual_line_index;
829 return IterationDecision::Continue;
830 });
831 }
832
833 if (is_enabled() && is_focused() && !focus_preempted() && m_cursor_state && !is_displayonly())
834 painter.fill_rect(cursor_content_rect(), palette().text_cursor());
835}
836
837Optional<UISize> TextEditor::calculated_min_size() const
838{
839 if (is_multi_line())
840 return AbstractScrollableWidget::calculated_min_size();
841 auto m = content_margins();
842 return UISize { m.left() + m.right(), m.top() + m.bottom() };
843}
844
845void TextEditor::select_all()
846{
847 TextPosition start_of_document { 0, 0 };
848 TextPosition end_of_document { line_count() - 1, line(line_count() - 1).length() };
849 m_selection.set(end_of_document, start_of_document);
850 did_update_selection();
851 set_cursor(start_of_document);
852 update();
853}
854
855void TextEditor::insert_emoji()
856{
857 if (!on_emoji_input || window()->blocks_emoji_input())
858 return;
859
860 auto emoji_input_dialog = EmojiInputDialog::construct(window());
861 if (emoji_input_dialog->exec() != EmojiInputDialog::ExecResult::OK)
862 return;
863
864 auto emoji_code_point = emoji_input_dialog->selected_emoji_text();
865 insert_at_cursor_or_replace_selection(emoji_code_point);
866}
867
868void TextEditor::set_or_clear_emoji_input_callback()
869{
870 switch (m_mode) {
871 case Editable:
872 on_emoji_input = [this](auto emoji) {
873 insert_at_cursor_or_replace_selection(emoji);
874 };
875 break;
876
877 case DisplayOnly:
878 case ReadOnly:
879 on_emoji_input = {};
880 break;
881
882 default:
883 VERIFY_NOT_REACHED();
884 }
885}
886
887void TextEditor::keydown_event(KeyEvent& event)
888{
889 if (!is_editable() && event.key() == KeyCode::Key_Tab)
890 return AbstractScrollableWidget::keydown_event(event);
891
892 if (m_autocomplete_box && m_autocomplete_box->is_visible() && (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Tab)) {
893 TemporaryChange change { m_should_keep_autocomplete_box, true };
894 if (m_autocomplete_box->apply_suggestion() == CodeComprehension::AutocompleteResultEntry::HideAutocompleteAfterApplying::Yes)
895 hide_autocomplete();
896 else
897 try_update_autocomplete();
898 return;
899 }
900
901 if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Escape) {
902 hide_autocomplete();
903 return;
904 }
905
906 if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Up) {
907 m_autocomplete_box->previous_suggestion();
908 return;
909 }
910
911 if (m_autocomplete_box && m_autocomplete_box->is_visible() && event.key() == KeyCode::Key_Down) {
912 m_autocomplete_box->next_suggestion();
913 return;
914 }
915
916 if (is_single_line()) {
917 if (event.key() == KeyCode::Key_Tab)
918 return AbstractScrollableWidget::keydown_event(event);
919
920 if (event.modifiers() == KeyModifier::Mod_Shift && event.key() == KeyCode::Key_Return) {
921 if (on_shift_return_pressed)
922 on_shift_return_pressed();
923 return;
924 }
925
926 if (event.modifiers() == KeyModifier::Mod_Ctrl && event.key() == KeyCode::Key_Return) {
927 if (on_ctrl_return_pressed)
928 on_ctrl_return_pressed();
929 return;
930 }
931
932 if (event.key() == KeyCode::Key_Return) {
933 if (on_return_pressed)
934 on_return_pressed();
935 return;
936 }
937
938 if (event.key() == KeyCode::Key_Up) {
939 if (on_up_pressed)
940 on_up_pressed();
941 return;
942 }
943
944 if (event.key() == KeyCode::Key_Down) {
945 if (on_down_pressed)
946 on_down_pressed();
947 return;
948 }
949
950 if (event.key() == KeyCode::Key_PageUp) {
951 if (on_pageup_pressed)
952 on_pageup_pressed();
953 return;
954 }
955
956 if (event.key() == KeyCode::Key_PageDown) {
957 if (on_pagedown_pressed)
958 on_pagedown_pressed();
959 return;
960 }
961
962 } else if (!is_multi_line()) {
963 VERIFY_NOT_REACHED();
964 }
965
966 ArmedScopeGuard update_autocomplete { [&] {
967 try_update_autocomplete();
968 } };
969
970 if (is_multi_line() && !event.alt() && event.ctrl() && event.key() == KeyCode::Key_Return) {
971 if (!is_editable())
972 return;
973
974 size_t indent_length = current_line().leading_spaces();
975 DeprecatedString indent = current_line().to_utf8().substring(0, indent_length);
976 auto insert_pos = event.shift() ? InsertLineCommand::InsertPosition::Above : InsertLineCommand::InsertPosition::Below;
977 execute<InsertLineCommand>(m_cursor, move(indent), insert_pos);
978 return;
979 }
980
981 if (is_multi_line() && !event.shift() && !event.alt() && event.ctrl() && event.key() == KeyCode::Key_Space) {
982 if (m_autocomplete_provider) {
983 try_show_autocomplete(UserRequestedAutocomplete::Yes);
984 update_autocomplete.disarm();
985 return;
986 }
987 }
988
989 if (is_multi_line() && !event.shift() && !event.alt() && event.ctrl() && event.key() == KeyCode::Key_F) {
990 m_search_banner->show();
991 return;
992 }
993
994 if (m_editing_engine->on_key(event))
995 return;
996
997 if (event.key() == KeyCode::Key_Escape) {
998 if (on_escape_pressed)
999 on_escape_pressed();
1000 return;
1001 }
1002
1003 if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) {
1004 if (m_autocomplete_box)
1005 hide_autocomplete();
1006 return;
1007 }
1008
1009 if (event.key() == KeyCode::Key_Tab) {
1010 if (has_selection()) {
1011 if (event.modifiers() == Mod_Shift) {
1012 unindent_selection();
1013 return;
1014 }
1015 if (is_indenting_selection()) {
1016 indent_selection();
1017 return;
1018 }
1019 } else {
1020 if (event.modifiers() == Mod_Shift) {
1021 unindent_line();
1022 return;
1023 }
1024 }
1025 }
1026
1027 if (event.key() == KeyCode::Key_Delete) {
1028 if (!is_editable())
1029 return;
1030 if (m_autocomplete_box)
1031 hide_autocomplete();
1032 if (has_selection()) {
1033 delete_selection();
1034 did_update_selection();
1035 return;
1036 }
1037
1038 if (m_cursor.column() < current_line().length()) {
1039 // Delete within line
1040 int erase_count = 1;
1041 if (event.modifiers() == Mod_Ctrl) {
1042 auto word_break_pos = document().first_word_break_after(m_cursor);
1043 erase_count = word_break_pos.column() - m_cursor.column();
1044 } else {
1045 auto grapheme_break_position = document().get_next_grapheme_cluster_boundary(m_cursor);
1046 erase_count = grapheme_break_position - m_cursor.column();
1047 }
1048 TextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + erase_count });
1049 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1050 return;
1051 }
1052 if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
1053 // Delete at end of line; merge with next line
1054 size_t erase_count = 0;
1055 if (event.modifiers() == Mod_Ctrl) {
1056 erase_count = document().first_word_break_after({ m_cursor.line() + 1, 0 }).column();
1057 }
1058 TextRange erased_range(m_cursor, { m_cursor.line() + 1, erase_count });
1059 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1060 return;
1061 }
1062 return;
1063 }
1064
1065 if (event.key() == KeyCode::Key_Backspace) {
1066 if (!is_editable())
1067 return;
1068 if (m_autocomplete_box)
1069 hide_autocomplete();
1070 if (has_selection()) {
1071 delete_selection();
1072 did_update_selection();
1073 return;
1074 }
1075 if (m_cursor.column() > 0) {
1076 int erase_count = 1;
1077 if (event.modifiers() == Mod_Ctrl) {
1078 auto word_break_pos = document().first_word_break_before(m_cursor, true);
1079 erase_count = m_cursor.column() - word_break_pos.column();
1080 } else if (current_line().first_non_whitespace_column() >= m_cursor.column()) {
1081 int new_column;
1082 if (m_cursor.column() % m_soft_tab_width == 0)
1083 new_column = m_cursor.column() - m_soft_tab_width;
1084 else
1085 new_column = (m_cursor.column() / m_soft_tab_width) * m_soft_tab_width;
1086 erase_count = m_cursor.column() - new_column;
1087 } else {
1088 auto grapheme_break_position = document().get_previous_grapheme_cluster_boundary(m_cursor);
1089 erase_count = m_cursor.column() - grapheme_break_position;
1090 }
1091
1092 // Backspace within line
1093 TextRange erased_range({ m_cursor.line(), m_cursor.column() - erase_count }, m_cursor);
1094 auto erased_text = document().text_in_range(erased_range);
1095 execute<RemoveTextCommand>(erased_text, erased_range);
1096 return;
1097 }
1098 if (m_cursor.column() == 0 && m_cursor.line() != 0) {
1099 // Backspace at column 0; merge with previous line
1100 size_t previous_length = line(m_cursor.line() - 1).length();
1101 TextRange erased_range({ m_cursor.line() - 1, previous_length }, m_cursor);
1102 execute<RemoveTextCommand>("\n", erased_range);
1103 return;
1104 }
1105 return;
1106 }
1107
1108 // AltGr is emulated as Ctrl+Alt; if Ctrl is set check if it's not for AltGr
1109 if ((!event.ctrl() || event.altgr()) && !event.alt() && event.code_point() != 0) {
1110 TemporaryChange change { m_should_keep_autocomplete_box, true };
1111 add_code_point(event.code_point());
1112 return;
1113 }
1114
1115 if (event.ctrl() && event.key() == KeyCode::Key_Slash) {
1116 if (m_highlighter != nullptr) {
1117 auto prefix = m_highlighter->comment_prefix().value_or(""sv);
1118 auto suffix = m_highlighter->comment_suffix().value_or(""sv);
1119 auto range = has_selection() ? selection().normalized() : TextRange { { m_cursor.line(), m_cursor.column() }, { m_cursor.line(), m_cursor.column() } };
1120
1121 auto is_already_commented = true;
1122 for (size_t i = range.start().line(); i <= range.end().line(); i++) {
1123 auto text = m_document->line(i).to_utf8().trim_whitespace();
1124 if (!(text.starts_with(prefix) && text.ends_with(suffix))) {
1125 is_already_commented = false;
1126 break;
1127 }
1128 }
1129
1130 if (is_already_commented)
1131 execute<UncommentSelection>(prefix, suffix, range);
1132 else
1133 execute<CommentSelection>(prefix, suffix, range);
1134
1135 return;
1136 }
1137 }
1138
1139 event.ignore();
1140}
1141
1142bool TextEditor::is_indenting_selection()
1143{
1144 auto const selection_start = m_selection.start() > m_selection.end() ? m_selection.end() : m_selection.start();
1145 auto const selection_end = m_selection.end() > m_selection.start() ? m_selection.end() : m_selection.start();
1146 auto const whole_line_selected = selection_end.column() - selection_start.column() >= current_line().length() - current_line().first_non_whitespace_column();
1147 auto const on_same_line = selection_start.line() == selection_end.line();
1148
1149 if (has_selection() && (whole_line_selected || !on_same_line)) {
1150 return true;
1151 }
1152
1153 return false;
1154}
1155
1156void TextEditor::indent_selection()
1157{
1158 auto const selection_start = m_selection.start() > m_selection.end() ? m_selection.end() : m_selection.start();
1159 auto const selection_end = m_selection.end() > m_selection.start() ? m_selection.end() : m_selection.start();
1160
1161 if (is_indenting_selection()) {
1162 execute<IndentSelection>(m_soft_tab_width, TextRange(selection_start, selection_end));
1163 m_selection.set_start({ selection_start.line(), selection_start.column() + m_soft_tab_width });
1164 m_selection.set_end({ selection_end.line(), selection_end.column() + m_soft_tab_width });
1165 set_cursor({ m_cursor.line(), m_cursor.column() + m_soft_tab_width });
1166 }
1167}
1168
1169void TextEditor::unindent_selection()
1170{
1171 auto const selection_start = m_selection.start() > m_selection.end() ? m_selection.end() : m_selection.start();
1172 auto const selection_end = m_selection.end() > m_selection.start() ? m_selection.end() : m_selection.start();
1173
1174 if (current_line().first_non_whitespace_column() != 0) {
1175 if (current_line().first_non_whitespace_column() > m_soft_tab_width && selection_start.column() != 0) {
1176 m_selection.set_start({ selection_start.line(), selection_start.column() - m_soft_tab_width });
1177 m_selection.set_end({ selection_end.line(), selection_end.column() - m_soft_tab_width });
1178 } else if (selection_start.column() != 0) {
1179 m_selection.set_start({ selection_start.line(), selection_start.column() - current_line().leading_spaces() });
1180 m_selection.set_end({ selection_end.line(), selection_end.column() - current_line().leading_spaces() });
1181 }
1182 execute<UnindentSelection>(m_soft_tab_width, TextRange(selection_start, selection_end));
1183 }
1184}
1185
1186void TextEditor::unindent_line()
1187{
1188 if (current_line().first_non_whitespace_column() != 0) {
1189 auto const unindent_size = current_line().leading_spaces() < m_soft_tab_width ? current_line().leading_spaces() : m_soft_tab_width;
1190 auto const temp_column = m_cursor.column();
1191
1192 execute<UnindentSelection>(unindent_size, TextRange({ m_cursor.line(), 0 }, { m_cursor.line(), line(m_cursor.line()).length() }));
1193
1194 set_cursor({ m_cursor.line(), temp_column <= unindent_size ? 0 : temp_column - unindent_size });
1195 }
1196}
1197
1198void TextEditor::delete_previous_word()
1199{
1200 TextRange to_erase(document().first_word_before(m_cursor, true), m_cursor);
1201 execute<RemoveTextCommand>(document().text_in_range(to_erase), to_erase);
1202}
1203
1204void TextEditor::delete_current_line()
1205{
1206 if (has_selection())
1207 return delete_selection();
1208
1209 TextPosition start;
1210 TextPosition end;
1211 if (m_cursor.line() == 0 && line_count() == 1) {
1212 start = { 0, 0 };
1213 end = { 0, line(0).length() };
1214 } else if (m_cursor.line() == line_count() - 1) {
1215 start = { m_cursor.line() - 1, line(m_cursor.line() - 1).length() };
1216 end = { m_cursor.line(), line(m_cursor.line()).length() };
1217 } else {
1218 start = { m_cursor.line(), 0 };
1219 end = { m_cursor.line() + 1, 0 };
1220 }
1221
1222 TextRange erased_range(start, end);
1223 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1224}
1225
1226void TextEditor::delete_previous_char()
1227{
1228 if (!is_editable())
1229 return;
1230
1231 if (has_selection())
1232 return delete_selection();
1233
1234 TextRange to_erase({ m_cursor.line(), m_cursor.column() - 1 }, m_cursor);
1235 if (m_cursor.column() == 0 && m_cursor.line() != 0) {
1236 size_t prev_line_len = line(m_cursor.line() - 1).length();
1237 to_erase.set_start({ m_cursor.line() - 1, prev_line_len });
1238 }
1239
1240 execute<RemoveTextCommand>(document().text_in_range(to_erase), to_erase);
1241}
1242
1243void TextEditor::delete_from_line_start_to_cursor()
1244{
1245 TextPosition start(m_cursor.line(), current_line().first_non_whitespace_column());
1246 TextRange to_erase(start, m_cursor);
1247 execute<RemoveTextCommand>(document().text_in_range(to_erase), to_erase);
1248}
1249
1250void TextEditor::do_delete()
1251{
1252 if (!is_editable())
1253 return;
1254
1255 if (has_selection())
1256 return delete_selection();
1257
1258 if (m_cursor.column() < current_line().length()) {
1259 // Delete within line
1260 TextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + 1 });
1261 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1262 return;
1263 }
1264 if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) {
1265 // Delete at end of line; merge with next line
1266 TextRange erased_range(m_cursor, { m_cursor.line() + 1, 0 });
1267 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1268 return;
1269 }
1270}
1271
1272void TextEditor::add_code_point(u32 code_point)
1273{
1274 if (!is_editable())
1275 return;
1276
1277 StringBuilder sb;
1278 sb.append_code_point(code_point);
1279
1280 if (should_autocomplete_automatically()) {
1281 if (sb.string_view().is_whitespace())
1282 m_autocomplete_timer->stop();
1283 else
1284 m_autocomplete_timer->start();
1285 }
1286 insert_at_cursor_or_replace_selection(sb.to_deprecated_string());
1287};
1288
1289void TextEditor::reset_cursor_blink()
1290{
1291 m_cursor_state = true;
1292 update_cursor();
1293 stop_timer();
1294 start_timer(500);
1295}
1296
1297void TextEditor::update_selection(bool is_selecting)
1298{
1299 if (is_selecting && !selection().is_valid()) {
1300 selection().set(cursor(), {});
1301 did_update_selection();
1302 update();
1303 return;
1304 }
1305 if (!is_selecting && selection().is_valid()) {
1306 selection().clear();
1307 did_update_selection();
1308 update();
1309 return;
1310 }
1311 if (is_selecting && selection().start().is_valid()) {
1312 selection().set_end(cursor());
1313 did_update_selection();
1314 update();
1315 return;
1316 }
1317}
1318
1319int TextEditor::content_x_for_position(TextPosition const& position) const
1320{
1321 auto& line = this->line(position.line());
1322 int x_offset = 0;
1323 switch (m_text_alignment) {
1324 case Gfx::TextAlignment::CenterLeft:
1325 for_each_visual_line(position.line(), [&](Gfx::IntRect const&, auto& visual_line_view, size_t start_of_visual_line, bool is_last_visual_line) {
1326 size_t offset_in_visual_line = position.column() - start_of_visual_line;
1327 auto before_line_end = is_last_visual_line ? (offset_in_visual_line <= visual_line_view.length()) : (offset_in_visual_line < visual_line_view.length());
1328 if (position.column() >= start_of_visual_line && before_line_end) {
1329 if (offset_in_visual_line == 0) {
1330 x_offset = 0;
1331 } else {
1332 x_offset = text_width_for_font(visual_line_view.substring_view(0, offset_in_visual_line), font());
1333 x_offset += font().glyph_spacing();
1334 }
1335 return IterationDecision::Break;
1336 }
1337 return IterationDecision::Continue;
1338 });
1339 return m_horizontal_content_padding + ((is_single_line() && icon()) ? (icon_size() + icon_padding()) : 0) + x_offset;
1340 case Gfx::TextAlignment::CenterRight:
1341 // FIXME
1342 VERIFY(!is_wrapping_enabled());
1343 return content_width() - m_horizontal_content_padding - (line.length() * fixed_glyph_width()) + (position.column() * fixed_glyph_width());
1344 default:
1345 VERIFY_NOT_REACHED();
1346 }
1347}
1348
1349int TextEditor::text_width_for_font(auto const& text, Gfx::Font const& font) const
1350{
1351 if (m_substitution_code_point.has_value())
1352 return font.width(substitution_code_point_view(text.length()));
1353 else
1354 return font.width(text);
1355}
1356
1357Utf32View TextEditor::substitution_code_point_view(size_t length) const
1358{
1359 VERIFY(m_substitution_code_point.has_value());
1360 if (!m_substitution_string_data)
1361 m_substitution_string_data = make<Vector<u32>>();
1362 if (!m_substitution_string_data->is_empty())
1363 VERIFY(m_substitution_string_data->first() == m_substitution_code_point);
1364 while (m_substitution_string_data->size() < length)
1365 m_substitution_string_data->append(m_substitution_code_point.value());
1366 return Utf32View { m_substitution_string_data->data(), length };
1367}
1368
1369Gfx::IntRect TextEditor::content_rect_for_position(TextPosition const& position) const
1370{
1371 if (!position.is_valid())
1372 return {};
1373 VERIFY(!lines().is_empty());
1374 VERIFY(position.column() <= (current_line().length() + 1));
1375
1376 int x = content_x_for_position(position);
1377
1378 if (is_single_line()) {
1379 Gfx::IntRect rect { x, 0, 1, line_height() };
1380 rect.center_vertically_within({ {}, frame_inner_rect().size() });
1381 return rect;
1382 }
1383
1384 Gfx::IntRect rect;
1385 for_each_visual_line(position.line(), [&](Gfx::IntRect const& visual_line_rect, auto& view, size_t start_of_visual_line, bool is_last_visual_line) {
1386 auto before_line_end = is_last_visual_line ? ((position.column() - start_of_visual_line) <= view.length()) : ((position.column() - start_of_visual_line) < view.length());
1387 if (position.column() >= start_of_visual_line && before_line_end) {
1388 // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
1389 // *and* included in what we get from content_x_for_position().
1390 rect = {
1391 visual_line_rect.x() + x - (m_horizontal_content_padding),
1392 visual_line_rect.y(),
1393 m_editing_engine->cursor_width() == CursorWidth::WIDE ? 7 : 1,
1394 line_height()
1395 };
1396 return IterationDecision::Break;
1397 }
1398 return IterationDecision::Continue;
1399 });
1400 return rect;
1401}
1402
1403Gfx::IntRect TextEditor::cursor_content_rect() const
1404{
1405 return content_rect_for_position(m_cursor);
1406}
1407
1408Gfx::IntRect TextEditor::line_widget_rect(size_t line_index) const
1409{
1410 auto rect = line_content_rect(line_index);
1411 rect.set_x(frame_thickness());
1412 rect.set_width(frame_inner_rect().width());
1413 rect.translate_by(0, -(vertical_scrollbar().value()));
1414 rect.translate_by(0, frame_thickness());
1415 rect.translate_by(0, height_occupied_by_banner_widget());
1416 rect.intersect(frame_inner_rect());
1417 return rect;
1418}
1419
1420void TextEditor::scroll_position_into_view(TextPosition const& position)
1421{
1422 auto rect = content_rect_for_position(position);
1423 if (position.column() == 0)
1424 rect.set_x(content_x_for_position({ position.line(), 0 }) - 2);
1425 else if (position.column() == line(position.line()).length())
1426 rect.set_x(content_x_for_position({ position.line(), line(position.line()).length() }) + 2);
1427 scroll_into_view(rect, true, true);
1428}
1429
1430void TextEditor::scroll_cursor_into_view()
1431{
1432 if (!m_reflow_deferred)
1433 scroll_position_into_view(m_cursor);
1434}
1435
1436Gfx::IntRect TextEditor::line_content_rect(size_t line_index) const
1437{
1438 auto& line = this->line(line_index);
1439 if (is_single_line()) {
1440 Gfx::IntRect line_rect = { content_x_for_position({ line_index, 0 }), 0, text_width_for_font(line.view(), font()), font().pixel_size_rounded_up() + 4 };
1441 line_rect.center_vertically_within({ {}, frame_inner_rect().size() });
1442 return line_rect;
1443 }
1444 return m_line_visual_data[line_index]->visual_rect;
1445}
1446
1447void TextEditor::set_cursor_and_focus_line(size_t line, size_t column)
1448{
1449 u_int index_max = line_count() - 1;
1450 set_cursor(line, column);
1451 if (line > 1 && line < index_max) {
1452 int headroom = frame_inner_rect().height() / 3;
1453 do {
1454 auto const& line_data = m_line_visual_data[line];
1455 headroom -= line_data->visual_rect.height();
1456 line--;
1457 } while (line > 0 && headroom > 0);
1458
1459 Gfx::IntRect rect = { 0, line_content_rect(line).y(),
1460 1, frame_inner_rect().height() };
1461 scroll_into_view(rect, false, true);
1462 }
1463}
1464
1465void TextEditor::update_cursor()
1466{
1467 update(line_widget_rect(m_cursor.line()));
1468}
1469
1470void TextEditor::set_cursor(size_t line, size_t column)
1471{
1472 set_cursor({ line, column });
1473}
1474
1475void TextEditor::set_cursor(TextPosition const& a_position)
1476{
1477 VERIFY(!lines().is_empty());
1478
1479 TextPosition position = a_position;
1480
1481 if (position.line() >= line_count())
1482 position.set_line(line_count() - 1);
1483
1484 if (position.column() > lines()[position.line()]->length())
1485 position.set_column(lines()[position.line()]->length());
1486
1487 if (m_cursor != position && is_visual_data_up_to_date()) {
1488 // NOTE: If the old cursor is no longer valid, repaint everything just in case.
1489 auto old_cursor_line_rect = m_cursor.line() < line_count()
1490 ? line_widget_rect(m_cursor.line())
1491 : rect();
1492 m_cursor = position;
1493 m_cursor_state = true;
1494 scroll_cursor_into_view();
1495 update(old_cursor_line_rect);
1496 update_cursor();
1497 } else if (m_cursor != position) {
1498 m_cursor = position;
1499 m_cursor_state = true;
1500 }
1501 cursor_did_change();
1502 if (on_cursor_change)
1503 on_cursor_change();
1504 if (m_highlighter)
1505 m_highlighter->cursor_did_change();
1506 if (m_relative_line_number)
1507 update();
1508}
1509
1510void TextEditor::set_cursor_to_text_position(Gfx::IntPoint position)
1511{
1512 auto visual_position = text_position_at(position);
1513 size_t physical_column = 0;
1514
1515 auto const& line = document().line(visual_position.line());
1516 size_t boundary_index = 0;
1517
1518 Unicode::for_each_grapheme_segmentation_boundary(line.view(), [&](auto boundary) {
1519 physical_column = boundary;
1520
1521 if (boundary_index++ >= visual_position.column())
1522 return IterationDecision::Break;
1523 return IterationDecision::Continue;
1524 });
1525
1526 set_cursor({ visual_position.line(), physical_column });
1527}
1528
1529void TextEditor::set_cursor_to_end_of_visual_line()
1530{
1531 for_each_visual_line(m_cursor.line(), [&](auto const&, auto& view, size_t start_of_visual_line, auto) {
1532 if (m_cursor.column() < start_of_visual_line)
1533 return IterationDecision::Continue;
1534 if ((m_cursor.column() - start_of_visual_line) >= view.length())
1535 return IterationDecision::Continue;
1536
1537 set_cursor(m_cursor.line(), start_of_visual_line + view.length());
1538 return IterationDecision::Break;
1539 });
1540}
1541
1542void TextEditor::focusin_event(FocusEvent& event)
1543{
1544 if (event.source() == FocusSource::Keyboard)
1545 select_all();
1546 m_cursor_state = true;
1547 update_cursor();
1548 stop_timer();
1549 start_timer(500);
1550 if (on_focusin)
1551 on_focusin();
1552}
1553
1554void TextEditor::focusout_event(FocusEvent&)
1555{
1556 if (is_displayonly() && has_selection())
1557 m_selection.clear();
1558 stop_timer();
1559 if (on_focusout)
1560 on_focusout();
1561}
1562
1563void TextEditor::timer_event(Core::TimerEvent&)
1564{
1565 m_cursor_state = !m_cursor_state;
1566 if (is_focused())
1567 update_cursor();
1568}
1569
1570ErrorOr<void> TextEditor::write_to_file(StringView path)
1571{
1572 auto file = TRY(Core::File::open(path, Core::File::OpenMode::Write | Core::File::OpenMode::Truncate));
1573 TRY(write_to_file(*file));
1574 return {};
1575}
1576
1577ErrorOr<void> TextEditor::write_to_file(Core::File& file)
1578{
1579 off_t file_size = 0;
1580 if (line_count() == 1 && line(0).is_empty()) {
1581 // Truncate to zero.
1582 } else {
1583 // Compute the final file size and ftruncate() to make writing fast.
1584 // FIXME: Remove this once the kernel is smart enough to do this instead.
1585 for (size_t i = 0; i < line_count(); ++i)
1586 file_size += line(i).length();
1587 file_size += line_count();
1588 }
1589
1590 TRY(file.truncate(file_size));
1591
1592 if (file_size == 0) {
1593 // A size 0 file doesn't need a data copy.
1594 } else {
1595 for (size_t i = 0; i < line_count(); ++i) {
1596 TRY(file.write_until_depleted(line(i).to_utf8().bytes()));
1597 TRY(file.write_until_depleted("\n"sv.bytes()));
1598 }
1599 }
1600 document().set_unmodified();
1601 return {};
1602}
1603
1604DeprecatedString TextEditor::text() const
1605{
1606 return document().text();
1607}
1608
1609void TextEditor::clear()
1610{
1611 document().remove_all_lines();
1612 document().append_line(make<TextDocumentLine>(document()));
1613 m_selection.clear();
1614 did_update_selection();
1615 set_cursor(0, 0);
1616 update();
1617}
1618
1619DeprecatedString TextEditor::selected_text() const
1620{
1621 if (!has_selection())
1622 return {};
1623
1624 return document().text_in_range(m_selection);
1625}
1626
1627size_t TextEditor::number_of_selected_words() const
1628{
1629 if (!has_selection())
1630 return 0;
1631
1632 size_t word_count = 0;
1633 bool in_word = false;
1634 auto selected_text = this->selected_text();
1635 for (char c : selected_text) {
1636 if (in_word && is_ascii_space(c)) {
1637 in_word = false;
1638 word_count++;
1639 continue;
1640 }
1641 if (!in_word && !is_ascii_space(c))
1642 in_word = true;
1643 }
1644 if (in_word)
1645 word_count++;
1646
1647 return word_count;
1648}
1649
1650size_t TextEditor::number_of_words() const
1651{
1652 if (document().is_empty())
1653 return 0;
1654
1655 size_t word_count = 0;
1656 bool in_word = false;
1657 auto text = this->text();
1658 for (char c : text) {
1659 if (in_word && is_ascii_space(c)) {
1660 in_word = false;
1661 word_count++;
1662 continue;
1663 }
1664 if (!in_word && !is_ascii_space(c))
1665 in_word = true;
1666 }
1667 if (in_word)
1668 word_count++;
1669
1670 return word_count;
1671}
1672
1673void TextEditor::delete_selection()
1674{
1675 auto selection = normalized_selection();
1676 auto selected = selected_text();
1677 m_selection.clear();
1678 execute<RemoveTextCommand>(selected, selection);
1679 did_update_selection();
1680 did_change();
1681 set_cursor(selection.start());
1682 update();
1683}
1684
1685void TextEditor::delete_text_range(TextRange range)
1686{
1687 auto normalized_range = range.normalized();
1688 execute<RemoveTextCommand>(document().text_in_range(normalized_range), normalized_range);
1689 did_change();
1690 set_cursor(normalized_range.start());
1691 update();
1692}
1693
1694void TextEditor::insert_at_cursor_or_replace_selection(StringView text)
1695{
1696 ReflowDeferrer defer(*this);
1697 VERIFY(is_editable());
1698 if (has_selection())
1699 delete_selection();
1700
1701 // Check if adding a newline leaves the previous line as just whitespace.
1702 auto const clear_length = m_cursor.column();
1703 auto const should_clear_last_line = text == "\n"
1704 && clear_length > 0
1705 && current_line().leading_spaces() == clear_length;
1706
1707 execute<InsertTextCommand>(text, m_cursor);
1708
1709 if (should_clear_last_line) { // If it does leave just whitespace, clear it.
1710 auto const original_cursor_position = cursor();
1711 TextPosition start(original_cursor_position.line() - 1, 0);
1712 TextPosition end(original_cursor_position.line() - 1, clear_length);
1713 TextRange erased_range(start, end);
1714 execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range);
1715 set_cursor(original_cursor_position);
1716 }
1717}
1718
1719void TextEditor::replace_all_text_without_resetting_undo_stack(StringView text)
1720{
1721 auto start = GUI::TextPosition(0, 0);
1722 auto last_line_index = line_count() - 1;
1723 auto end = GUI::TextPosition(last_line_index, line(last_line_index).length());
1724 auto range = GUI::TextRange(start, end);
1725 auto normalized_range = range.normalized();
1726 execute<ReplaceAllTextCommand>(text, range, "GML Playground Format Text");
1727 did_change();
1728 set_cursor(normalized_range.start());
1729 update();
1730}
1731
1732void TextEditor::cut()
1733{
1734 if (!is_editable())
1735 return;
1736 auto selected_text = this->selected_text();
1737 dbgln_if(TEXTEDITOR_DEBUG, "Cut: \"{}\"", selected_text);
1738 Clipboard::the().set_plain_text(selected_text);
1739 delete_selection();
1740}
1741
1742void TextEditor::copy()
1743{
1744 auto selected_text = this->selected_text();
1745 dbgln_if(TEXTEDITOR_DEBUG, "Copy: \"{}\"\n", selected_text);
1746 Clipboard::the().set_plain_text(selected_text);
1747}
1748
1749void TextEditor::paste()
1750{
1751 if (!is_editable())
1752 return;
1753
1754 auto [data, mime_type, _] = GUI::Clipboard::the().fetch_data_and_type();
1755 if (!mime_type.starts_with("text/"sv))
1756 return;
1757
1758 if (data.is_empty())
1759 return;
1760
1761 dbgln_if(TEXTEDITOR_DEBUG, "Paste: \"{}\"", DeprecatedString::copy(data));
1762
1763 TemporaryChange change(m_automatic_indentation_enabled, false);
1764 insert_at_cursor_or_replace_selection(data);
1765}
1766
1767void TextEditor::defer_reflow()
1768{
1769 ++m_reflow_deferred;
1770}
1771
1772void TextEditor::undefer_reflow()
1773{
1774 VERIFY(m_reflow_deferred);
1775 if (!--m_reflow_deferred) {
1776 if (m_reflow_requested) {
1777 recompute_all_visual_lines();
1778 scroll_cursor_into_view();
1779 }
1780 }
1781}
1782
1783void TextEditor::try_show_autocomplete(UserRequestedAutocomplete user_requested_autocomplete)
1784{
1785 force_update_autocomplete([&, user_requested_autocomplete = move(user_requested_autocomplete)] {
1786 if (user_requested_autocomplete == Yes || m_autocomplete_box->has_suggestions()) {
1787 auto position = content_rect_for_position(cursor()).translated(0, -visible_content_rect().y()).bottom_right().translated(screen_relative_rect().top_left().translated(ruler_width(), 0).translated(10, 5));
1788 m_autocomplete_box->show(position);
1789 }
1790 });
1791}
1792
1793void TextEditor::try_update_autocomplete(Function<void()> callback)
1794{
1795 if (m_autocomplete_box && m_autocomplete_box->is_visible())
1796 force_update_autocomplete(move(callback));
1797}
1798
1799void TextEditor::force_update_autocomplete(Function<void()> callback)
1800{
1801 if (m_autocomplete_provider) {
1802 m_autocomplete_provider->provide_completions([&, callback = move(callback)](auto completions) {
1803 m_autocomplete_box->update_suggestions(move(completions));
1804 if (callback)
1805 callback();
1806 });
1807 }
1808}
1809
1810void TextEditor::hide_autocomplete_if_needed()
1811{
1812 if (!m_should_keep_autocomplete_box)
1813 hide_autocomplete();
1814}
1815
1816void TextEditor::hide_autocomplete()
1817{
1818 if (m_autocomplete_box) {
1819 m_autocomplete_box->close();
1820 if (m_autocomplete_timer)
1821 m_autocomplete_timer->stop();
1822 }
1823}
1824
1825void TextEditor::enter_event(Core::Event&)
1826{
1827 set_automatic_scrolling_timer_active(false);
1828}
1829
1830void TextEditor::leave_event(Core::Event&)
1831{
1832 if (m_in_drag_select)
1833 set_automatic_scrolling_timer_active(true);
1834}
1835
1836void TextEditor::did_change(AllowCallback allow_callback)
1837{
1838 update_content_size();
1839 recompute_all_visual_lines();
1840 hide_autocomplete_if_needed();
1841 m_needs_rehighlight = true;
1842 if (on_change && allow_callback == AllowCallback::Yes)
1843 on_change();
1844}
1845void TextEditor::set_mode(const Mode mode)
1846{
1847 if (m_mode == mode)
1848 return;
1849 m_mode = mode;
1850 switch (mode) {
1851 case Editable:
1852 m_cut_action->set_enabled(has_selection() && !text_is_secret());
1853 m_paste_action->set_enabled(true);
1854 m_insert_emoji_action->set_enabled(true);
1855 break;
1856 case DisplayOnly:
1857 case ReadOnly:
1858 m_cut_action->set_enabled(false);
1859 m_paste_action->set_enabled(false);
1860 m_insert_emoji_action->set_enabled(false);
1861 break;
1862 default:
1863 VERIFY_NOT_REACHED();
1864 }
1865
1866 set_or_clear_emoji_input_callback();
1867 set_editing_cursor();
1868}
1869
1870void TextEditor::set_editing_cursor()
1871{
1872 if (!is_displayonly())
1873 set_override_cursor(Gfx::StandardCursor::IBeam);
1874 else
1875 set_override_cursor(Gfx::StandardCursor::None);
1876}
1877
1878void TextEditor::did_update_selection()
1879{
1880 m_cut_action->set_enabled(is_editable() && has_selection() && !text_is_secret());
1881 m_copy_action->set_enabled(has_selection() && !text_is_secret());
1882 if (on_selection_change)
1883 on_selection_change();
1884 if (is_wrapping_enabled()) {
1885 // FIXME: Try to repaint less.
1886 update();
1887 }
1888}
1889
1890void TextEditor::context_menu_event(ContextMenuEvent& event)
1891{
1892 if (is_displayonly())
1893 return;
1894
1895 m_insert_emoji_action->set_enabled(on_emoji_input && !window()->blocks_emoji_input());
1896
1897 if (!m_context_menu) {
1898 m_context_menu = Menu::construct();
1899 m_context_menu->add_action(undo_action());
1900 m_context_menu->add_action(redo_action());
1901 m_context_menu->add_separator();
1902 m_context_menu->add_action(cut_action());
1903 m_context_menu->add_action(copy_action());
1904 m_context_menu->add_action(paste_action());
1905 m_context_menu->add_separator();
1906 m_context_menu->add_action(select_all_action());
1907 m_context_menu->add_action(insert_emoji_action());
1908 if (is_multi_line()) {
1909 m_context_menu->add_separator();
1910 m_context_menu->add_action(go_to_line_action());
1911 }
1912 if (!m_custom_context_menu_actions.is_empty()) {
1913 m_context_menu->add_separator();
1914 for (auto& action : m_custom_context_menu_actions) {
1915 m_context_menu->add_action(action);
1916 }
1917 }
1918 }
1919 m_context_menu->popup(event.screen_position());
1920}
1921
1922void TextEditor::set_text_alignment(Gfx::TextAlignment alignment)
1923{
1924 if (m_text_alignment == alignment)
1925 return;
1926 m_text_alignment = alignment;
1927 update();
1928}
1929
1930void TextEditor::resize_event(ResizeEvent& event)
1931{
1932 AbstractScrollableWidget::resize_event(event);
1933 update_content_size();
1934 recompute_all_visual_lines();
1935}
1936
1937void TextEditor::theme_change_event(ThemeChangeEvent& event)
1938{
1939 AbstractScrollableWidget::theme_change_event(event);
1940 m_needs_rehighlight = true;
1941}
1942
1943void TextEditor::set_selection(TextRange const& selection)
1944{
1945 if (m_selection == selection)
1946 return;
1947 m_selection = selection;
1948 set_cursor(m_selection.end());
1949 scroll_position_into_view(normalized_selection().start());
1950 update();
1951}
1952
1953void TextEditor::clear_selection()
1954{
1955 if (!has_selection())
1956 return;
1957 m_selection.clear();
1958 update();
1959}
1960
1961void TextEditor::recompute_all_visual_lines()
1962{
1963 if (m_reflow_deferred) {
1964 m_reflow_requested = true;
1965 return;
1966 }
1967
1968 m_reflow_requested = false;
1969
1970 int y_offset = 0;
1971 auto folded_regions = document().currently_folded_regions();
1972 auto folded_region_iterator = folded_regions.begin();
1973 for (size_t line_index = 0; line_index < line_count(); ++line_index) {
1974 recompute_visual_lines(line_index, folded_region_iterator);
1975 m_line_visual_data[line_index]->visual_rect.set_y(y_offset);
1976 y_offset += m_line_visual_data[line_index]->visual_rect.height();
1977 }
1978
1979 update_content_size();
1980}
1981
1982void TextEditor::ensure_cursor_is_valid()
1983{
1984 auto new_cursor = m_cursor;
1985 if (new_cursor.line() >= line_count())
1986 new_cursor.set_line(line_count() - 1);
1987 if (new_cursor.column() > line(new_cursor.line()).length())
1988 new_cursor.set_column(line(new_cursor.line()).length());
1989 if (m_cursor != new_cursor)
1990 set_cursor(new_cursor);
1991}
1992
1993size_t TextEditor::visual_line_containing(size_t line_index, size_t column) const
1994{
1995 size_t visual_line_index = 0;
1996 for_each_visual_line(line_index, [&](Gfx::IntRect const&, auto& view, size_t start_of_visual_line, [[maybe_unused]] bool is_last_visual_line) {
1997 if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length()))
1998 return IterationDecision::Break;
1999 ++visual_line_index;
2000 return IterationDecision::Continue;
2001 });
2002 return visual_line_index;
2003}
2004
2005void TextEditor::recompute_visual_lines(size_t line_index, Vector<TextDocumentFoldingRegion const&>::Iterator& folded_region_iterator)
2006{
2007 auto const& line = document().line(line_index);
2008 size_t line_width_so_far = 0;
2009
2010 auto& visual_data = m_line_visual_data[line_index];
2011 visual_data->visual_lines.clear_with_capacity();
2012
2013 auto available_width = visible_text_rect_in_inner_coordinates().width();
2014 auto glyph_spacing = font().glyph_spacing();
2015
2016 while (!folded_region_iterator.is_end() && folded_region_iterator->range.end() < TextPosition { line_index, 0 })
2017 ++folded_region_iterator;
2018 bool line_is_visible = true;
2019 if (!folded_region_iterator.is_end()) {
2020 if (folded_region_iterator->range.start().line() < line_index) {
2021 if (folded_region_iterator->range.end().line() > line_index) {
2022 line_is_visible = false;
2023 } else if (folded_region_iterator->range.end().line() == line_index) {
2024 ++folded_region_iterator;
2025 }
2026 }
2027 }
2028
2029 auto wrap_visual_lines_anywhere = [&]() {
2030 size_t start_of_visual_line = 0;
2031 for (auto it = line.view().begin(); it != line.view().end(); ++it) {
2032 auto it_before_computing_glyph_width = it;
2033 auto glyph_width = font().glyph_or_emoji_width(it);
2034
2035 if (line_width_so_far + glyph_width + glyph_spacing > available_width) {
2036 auto start_of_next_visual_line = line.view().iterator_offset(it_before_computing_glyph_width);
2037 visual_data->visual_lines.append(line.view().substring_view(start_of_visual_line, start_of_next_visual_line - start_of_visual_line));
2038 line_width_so_far = 0;
2039 start_of_visual_line = start_of_next_visual_line;
2040 }
2041
2042 line_width_so_far += glyph_width + glyph_spacing;
2043 }
2044
2045 visual_data->visual_lines.append(line.view().substring_view(start_of_visual_line, line.view().length() - start_of_visual_line));
2046 };
2047
2048 auto wrap_visual_lines_at_words = [&]() {
2049 size_t last_boundary = 0;
2050 size_t start_of_visual_line = 0;
2051
2052 Unicode::for_each_word_segmentation_boundary(line.view(), [&](auto boundary) {
2053 if (boundary == 0)
2054 return IterationDecision::Continue;
2055
2056 auto word = line.view().substring_view(last_boundary, boundary - last_boundary);
2057 auto word_width = font().width(word);
2058
2059 if (line_width_so_far + word_width + glyph_spacing > available_width) {
2060 visual_data->visual_lines.append(line.view().substring_view(start_of_visual_line, last_boundary - start_of_visual_line));
2061 line_width_so_far = 0;
2062 start_of_visual_line = last_boundary;
2063 }
2064
2065 line_width_so_far += word_width + glyph_spacing;
2066 last_boundary = boundary;
2067
2068 return IterationDecision::Continue;
2069 });
2070
2071 visual_data->visual_lines.append(line.view().substring_view(start_of_visual_line, line.view().length() - start_of_visual_line));
2072 };
2073
2074 if (line_is_visible) {
2075 switch (wrapping_mode()) {
2076 case WrappingMode::NoWrap:
2077 visual_data->visual_lines.append(line.view());
2078 break;
2079 case WrappingMode::WrapAnywhere:
2080 wrap_visual_lines_anywhere();
2081 break;
2082 case WrappingMode::WrapAtWords:
2083 wrap_visual_lines_at_words();
2084 break;
2085 }
2086 }
2087
2088 if (is_wrapping_enabled())
2089 visual_data->visual_rect = { m_horizontal_content_padding, 0, available_width, static_cast<int>(visual_data->visual_lines.size()) * line_height() };
2090 else
2091 visual_data->visual_rect = { m_horizontal_content_padding, 0, text_width_for_font(line.view(), font()), line_height() };
2092}
2093
2094template<typename Callback>
2095void TextEditor::for_each_visual_line(size_t line_index, Callback callback) const
2096{
2097 auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates();
2098 size_t visual_line_index = 0;
2099
2100 auto& line = document().line(line_index);
2101 auto& visual_data = m_line_visual_data[line_index];
2102
2103 for (auto visual_line_view : visual_data->visual_lines) {
2104 Gfx::IntRect visual_line_rect {
2105 visual_data->visual_rect.x(),
2106 visual_data->visual_rect.y() + ((int)visual_line_index * line_height()),
2107 text_width_for_font(visual_line_view, font()) + font().glyph_spacing(),
2108 line_height()
2109 };
2110 if (is_right_text_alignment(text_alignment()))
2111 visual_line_rect.set_right_without_resize(editor_visible_text_rect.right());
2112 if (is_single_line()) {
2113 visual_line_rect.center_vertically_within(editor_visible_text_rect);
2114 if (m_icon)
2115 visual_line_rect.translate_by(icon_size() + icon_padding(), 0);
2116 }
2117 size_t start_of_line = visual_line_view.code_points() - line.code_points();
2118 if (callback(visual_line_rect, visual_line_view, start_of_line, visual_line_index == visual_data->visual_lines.size() - 1) == IterationDecision::Break)
2119 break;
2120 ++visual_line_index;
2121 }
2122}
2123
2124void TextEditor::set_wrapping_mode(WrappingMode mode)
2125{
2126 if (m_wrapping_mode == mode)
2127 return;
2128
2129 m_wrapping_mode = mode;
2130 horizontal_scrollbar().set_visible(m_wrapping_mode == WrappingMode::NoWrap);
2131 update_content_size();
2132 recompute_all_visual_lines();
2133 update();
2134}
2135
2136void TextEditor::add_custom_context_menu_action(Action& action)
2137{
2138 m_custom_context_menu_actions.append(action);
2139}
2140
2141void TextEditor::did_change_font()
2142{
2143 vertical_scrollbar().set_step(line_height());
2144 recompute_all_visual_lines();
2145 update();
2146 AbstractScrollableWidget::did_change_font();
2147}
2148
2149void TextEditor::document_did_append_line()
2150{
2151 m_line_visual_data.append(make<LineVisualData>());
2152 recompute_all_visual_lines();
2153 update();
2154}
2155
2156void TextEditor::document_did_remove_line(size_t line_index)
2157{
2158 m_line_visual_data.remove(line_index);
2159 recompute_all_visual_lines();
2160 update();
2161}
2162
2163void TextEditor::document_did_remove_all_lines()
2164{
2165 m_line_visual_data.clear();
2166 recompute_all_visual_lines();
2167 update();
2168}
2169
2170void TextEditor::document_did_insert_line(size_t line_index)
2171{
2172 m_line_visual_data.insert(line_index, make<LineVisualData>());
2173 recompute_all_visual_lines();
2174 update();
2175}
2176
2177void TextEditor::document_did_change(AllowCallback allow_callback)
2178{
2179 did_change(allow_callback);
2180 update();
2181}
2182
2183void TextEditor::document_did_update_undo_stack()
2184{
2185 auto make_action_text = [](auto prefix, auto suffix) {
2186 StringBuilder builder;
2187 builder.append(prefix);
2188 if (suffix.has_value()) {
2189 builder.append(' ');
2190 builder.append(suffix.value());
2191 }
2192 return builder.to_deprecated_string();
2193 };
2194
2195 m_undo_action->set_enabled(can_undo() && !text_is_secret());
2196 m_redo_action->set_enabled(can_redo() && !text_is_secret());
2197
2198 m_undo_action->set_text(make_action_text("&Undo"sv, document().undo_stack().undo_action_text()));
2199 m_redo_action->set_text(make_action_text("&Redo"sv, document().undo_stack().redo_action_text()));
2200
2201 // FIXME: This is currently firing more often than it should.
2202 // Ideally we'd only send this out when the undo stack modified state actually changes.
2203 if (on_modified_change)
2204 on_modified_change(document().is_modified());
2205}
2206
2207void TextEditor::document_did_set_text(AllowCallback allow_callback)
2208{
2209 m_line_visual_data.clear();
2210 for (size_t i = 0; i < m_document->line_count(); ++i)
2211 m_line_visual_data.append(make<LineVisualData>());
2212 document_did_change(allow_callback);
2213}
2214
2215void TextEditor::document_did_set_cursor(TextPosition const& position)
2216{
2217 set_cursor(position);
2218}
2219
2220void TextEditor::cursor_did_change()
2221{
2222 hide_autocomplete_if_needed();
2223}
2224
2225void TextEditor::clipboard_content_did_change(DeprecatedString const& mime_type)
2226{
2227 m_paste_action->set_enabled(is_editable() && mime_type.starts_with("text/"sv));
2228}
2229
2230void TextEditor::set_document(TextDocument& document)
2231{
2232 if (m_document.ptr() == &document)
2233 return;
2234 if (m_document)
2235 m_document->unregister_client(*this);
2236 m_document = document;
2237 m_line_visual_data.clear();
2238 for (size_t i = 0; i < m_document->line_count(); ++i) {
2239 m_line_visual_data.append(make<LineVisualData>());
2240 }
2241 set_cursor(0, 0);
2242 if (has_selection())
2243 m_selection.clear();
2244 recompute_all_visual_lines();
2245 update();
2246 m_document->register_client(*this);
2247}
2248
2249void TextEditor::rehighlight_if_needed()
2250{
2251 if (!m_needs_rehighlight)
2252 return;
2253 force_rehighlight();
2254}
2255
2256void TextEditor::force_rehighlight()
2257{
2258 if (m_highlighter)
2259 m_highlighter->rehighlight(palette());
2260 m_needs_rehighlight = false;
2261}
2262
2263Syntax::Highlighter const* TextEditor::syntax_highlighter() const
2264{
2265 return m_highlighter.ptr();
2266}
2267
2268Syntax::Highlighter* TextEditor::syntax_highlighter()
2269{
2270 return m_highlighter.ptr();
2271}
2272
2273void TextEditor::set_syntax_highlighter(OwnPtr<Syntax::Highlighter> highlighter)
2274{
2275 if (m_highlighter)
2276 m_highlighter->detach();
2277 m_highlighter = move(highlighter);
2278 if (m_highlighter) {
2279 m_highlighter->attach(*this);
2280 m_needs_rehighlight = true;
2281 } else
2282 document().set_spans(Syntax::HighlighterClient::span_collection_index, {});
2283 if (on_highlighter_change)
2284 on_highlighter_change();
2285}
2286
2287AutocompleteProvider const* TextEditor::autocomplete_provider() const
2288{
2289 return m_autocomplete_provider.ptr();
2290}
2291
2292void TextEditor::set_autocomplete_provider(OwnPtr<AutocompleteProvider>&& provider)
2293{
2294 if (m_autocomplete_provider)
2295 m_autocomplete_provider->detach();
2296 m_autocomplete_provider = move(provider);
2297 if (m_autocomplete_provider) {
2298 m_autocomplete_provider->attach(*this);
2299 if (!m_autocomplete_box)
2300 m_autocomplete_box = make<AutocompleteBox>(*this);
2301 }
2302 if (m_autocomplete_box)
2303 hide_autocomplete();
2304}
2305
2306EditingEngine const* TextEditor::editing_engine() const
2307{
2308 return m_editing_engine.ptr();
2309}
2310
2311void TextEditor::set_editing_engine(OwnPtr<EditingEngine> editing_engine)
2312{
2313 if (m_editing_engine)
2314 m_editing_engine->detach();
2315 m_editing_engine = move(editing_engine);
2316
2317 VERIFY(m_editing_engine);
2318 m_editing_engine->attach(*this);
2319
2320 m_cursor_state = true;
2321 update_cursor();
2322 stop_timer();
2323 start_timer(500);
2324}
2325
2326int TextEditor::line_height() const
2327{
2328 return static_cast<int>(ceilf(font().preferred_line_height()));
2329}
2330
2331int TextEditor::fixed_glyph_width() const
2332{
2333 VERIFY(font().is_fixed_width());
2334 return font().glyph_width(' ');
2335}
2336
2337void TextEditor::set_icon(Gfx::Bitmap const* icon)
2338{
2339 if (m_icon == icon)
2340 return;
2341 m_icon = icon;
2342 update();
2343}
2344
2345void TextEditor::set_visualize_trailing_whitespace(bool enabled)
2346{
2347 if (m_visualize_trailing_whitespace == enabled)
2348 return;
2349 m_visualize_trailing_whitespace = enabled;
2350 update();
2351}
2352
2353void TextEditor::set_visualize_leading_whitespace(bool enabled)
2354{
2355 if (m_visualize_leading_whitespace == enabled)
2356 return;
2357 m_visualize_leading_whitespace = enabled;
2358 update();
2359}
2360
2361void TextEditor::set_should_autocomplete_automatically(bool value)
2362{
2363 if (value == should_autocomplete_automatically())
2364 return;
2365
2366 if (value) {
2367 VERIFY(m_autocomplete_provider);
2368 m_autocomplete_timer = Core::Timer::create_single_shot(m_automatic_autocomplete_delay_ms, [this] {
2369 if (m_autocomplete_box && !m_autocomplete_box->is_visible())
2370 try_show_autocomplete(UserRequestedAutocomplete::No);
2371 }).release_value_but_fixme_should_propagate_errors();
2372 return;
2373 }
2374
2375 remove_child(*m_autocomplete_timer);
2376 m_autocomplete_timer = nullptr;
2377}
2378
2379void TextEditor::set_substitution_code_point(Optional<u32> code_point)
2380{
2381 if (code_point.has_value())
2382 VERIFY(is_unicode(code_point.value()));
2383 m_substitution_string_data.clear();
2384 m_substitution_code_point = move(code_point);
2385}
2386
2387int TextEditor::number_of_visible_lines() const
2388{
2389 return visible_content_rect().height() / line_height();
2390}
2391
2392void TextEditor::set_relative_line_number(bool relative)
2393{
2394 if (m_relative_line_number == relative)
2395 return;
2396 m_relative_line_number = relative;
2397 recompute_all_visual_lines();
2398 update();
2399}
2400
2401void TextEditor::set_ruler_visible(bool visible)
2402{
2403 if (m_ruler_visible == visible)
2404 return;
2405 m_ruler_visible = visible;
2406 recompute_all_visual_lines();
2407 update();
2408}
2409
2410void TextEditor::set_gutter_visible(bool visible)
2411{
2412 if (m_gutter_visible == visible)
2413 return;
2414 m_gutter_visible = visible;
2415 recompute_all_visual_lines();
2416 update();
2417}
2418
2419void TextEditor::set_cursor_line_highlighting(bool highlighted)
2420{
2421 if (m_cursor_line_highlighting == highlighted)
2422 return;
2423 m_cursor_line_highlighting = highlighted;
2424 update();
2425}
2426
2427void TextEditor::undo()
2428{
2429 clear_selection();
2430 document().undo();
2431}
2432
2433void TextEditor::redo()
2434{
2435 clear_selection();
2436 document().redo();
2437}
2438
2439void TextEditor::set_text_is_secret(bool text_is_secret)
2440{
2441 m_text_is_secret = text_is_secret;
2442 document_did_update_undo_stack();
2443 did_update_selection();
2444}
2445
2446TextRange TextEditor::find_text(StringView needle, SearchDirection direction, GUI::TextDocument::SearchShouldWrap should_wrap, bool use_regex, bool match_case)
2447{
2448 GUI::TextRange range {};
2449 if (direction == SearchDirection::Forward) {
2450 range = document().find_next(needle,
2451 m_search_result_index.has_value() ? m_search_results[*m_search_result_index].end() : GUI::TextPosition {},
2452 should_wrap, use_regex, match_case);
2453 } else {
2454 range = document().find_previous(needle,
2455 m_search_result_index.has_value() ? m_search_results[*m_search_result_index].start() : GUI::TextPosition {},
2456 should_wrap, use_regex, match_case);
2457 }
2458
2459 if (!range.is_valid()) {
2460 reset_search_results();
2461 return {};
2462 }
2463
2464 auto all_results = document().find_all(needle, use_regex, match_case);
2465 on_search_results(range, all_results);
2466 return range;
2467}
2468
2469void TextEditor::reset_search_results()
2470{
2471 m_search_result_index.clear();
2472 m_search_results.clear();
2473 document().set_spans(search_results_span_collection_index, {});
2474 update();
2475}
2476
2477void TextEditor::on_search_results(GUI::TextRange current, Vector<GUI::TextRange> all_results)
2478{
2479 m_search_result_index.clear();
2480 m_search_results.clear();
2481
2482 set_cursor(current.start());
2483 if (auto it = all_results.find(current); it->is_valid())
2484 m_search_result_index = it.index();
2485 m_search_results = move(all_results);
2486
2487 Vector<GUI::TextDocumentSpan> spans;
2488 for (size_t i = 0; i < m_search_results.size(); ++i) {
2489 auto& result = m_search_results[i];
2490 GUI::TextDocumentSpan span;
2491 span.range = result;
2492 span.attributes.background_color = palette().hover_highlight();
2493 span.attributes.color = Color::from_argb(0xff000000); // So text without spans from a highlighter will have color
2494 if (i == m_search_result_index) {
2495 span.attributes.bold = true;
2496 span.attributes.underline = true;
2497 }
2498 spans.append(move(span));
2499 }
2500 document().set_spans(search_results_span_collection_index, move(spans));
2501 update();
2502}
2503
2504void TextEditor::highlighter_did_set_folding_regions(Vector<GUI::TextDocumentFoldingRegion> folding_regions)
2505{
2506 document().set_folding_regions(move(folding_regions));
2507 recompute_all_visual_lines();
2508}
2509
2510}