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