Serenity Operating System
at master 2510 lines 90 kB view raw
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}