| 
									
										
										
										
											2020-01-18 09:38:21 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> | 
					
						
							|  |  |  |  * All rights reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Redistribution and use in source and binary forms, with or without | 
					
						
							|  |  |  |  * modification, are permitted provided that the following conditions are met: | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 1. Redistributions of source code must retain the above copyright notice, this | 
					
						
							|  |  |  |  *    list of conditions and the following disclaimer. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 2. Redistributions in binary form must reproduce the above copyright notice, | 
					
						
							|  |  |  |  *    this list of conditions and the following disclaimer in the documentation | 
					
						
							|  |  |  |  *    and/or other materials provided with the distribution. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
					
						
							|  |  |  |  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
					
						
							|  |  |  |  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
					
						
							|  |  |  |  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
					
						
							|  |  |  |  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
					
						
							|  |  |  |  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
					
						
							|  |  |  |  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
					
						
							|  |  |  |  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
					
						
							|  |  |  |  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
					
						
							|  |  |  |  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  | #include <AK/QuickSort.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  | #include <AK/StringBuilder.h>
 | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  | #include <LibCore/Timer.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-06 20:33:02 +01:00
										 |  |  | #include <LibGUI/Action.h>
 | 
					
						
							|  |  |  | #include <LibGUI/Clipboard.h>
 | 
					
						
							|  |  |  | #include <LibGUI/FontDatabase.h>
 | 
					
						
							|  |  |  | #include <LibGUI/InputBox.h>
 | 
					
						
							|  |  |  | #include <LibGUI/Menu.h>
 | 
					
						
							|  |  |  | #include <LibGUI/Painter.h>
 | 
					
						
							|  |  |  | #include <LibGUI/ScrollBar.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  | #include <LibGUI/SyntaxHighlighter.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-06 20:33:02 +01:00
										 |  |  | #include <LibGUI/TextEditor.h>
 | 
					
						
							|  |  |  | #include <LibGUI/Window.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-14 23:02:47 +01:00
										 |  |  | #include <LibGfx/Bitmap.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-15 00:24:14 +01:00
										 |  |  | #include <LibGfx/Font.h>
 | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  | #include <LibGfx/Palette.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  | #include <ctype.h>
 | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  | #include <fcntl.h>
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  | #include <unistd.h>
 | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-19 23:09:15 +01:00
										 |  |  | //#define DEBUG_TEXTEDITOR
 | 
					
						
							| 
									
										
										
										
											2019-09-16 20:57:32 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | namespace GUI { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-23 12:07:13 +01:00
										 |  |  | TextEditor::TextEditor(Type type) | 
					
						
							|  |  |  |     : m_type(type) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-05-17 21:51:58 +02:00
										 |  |  |     set_accepts_emoji_input(true); | 
					
						
							| 
									
										
										
										
											2019-12-24 22:01:32 +01:00
										 |  |  |     set_background_role(ColorRole::Base); | 
					
						
							|  |  |  |     set_foreground_role(ColorRole::BaseText); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     set_document(TextDocument::create()); | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     set_scrollbars_enabled(is_multi_line()); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:40:40 +02:00
										 |  |  |     if (is_multi_line()) | 
					
						
							|  |  |  |         set_font(Gfx::Font::default_fixed_width_font()); | 
					
						
							| 
									
										
										
										
											2019-06-07 10:42:43 +02:00
										 |  |  |     vertical_scrollbar().set_step(line_height()); | 
					
						
							| 
									
										
										
										
											2019-03-19 01:41:00 +01:00
										 |  |  |     m_cursor = { 0, 0 }; | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  |     m_automatic_selection_scroll_timer = add<Core::Timer>(100, [this] { | 
					
						
							|  |  |  |         automatic_selection_scroll_timer_fired(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     m_automatic_selection_scroll_timer->stop(); | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  |     create_actions(); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | TextEditor::~TextEditor() | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     if (m_document) | 
					
						
							|  |  |  |         m_document->unregister_client(*this); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::create_actions() | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     m_undo_action = CommonActions::make_undo_action([&](auto&) { undo(); }, this); | 
					
						
							|  |  |  |     m_redo_action = CommonActions::make_redo_action([&](auto&) { redo(); }, this); | 
					
						
							| 
									
										
										
										
											2019-11-09 01:50:39 -06:00
										 |  |  |     m_undo_action->set_enabled(false); | 
					
						
							|  |  |  |     m_redo_action->set_enabled(false); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     m_cut_action = CommonActions::make_cut_action([&](auto&) { cut(); }, this); | 
					
						
							|  |  |  |     m_copy_action = CommonActions::make_copy_action([&](auto&) { copy(); }, this); | 
					
						
							|  |  |  |     m_paste_action = CommonActions::make_paste_action([&](auto&) { paste(); }, this); | 
					
						
							|  |  |  |     m_delete_action = CommonActions::make_delete_action([&](auto&) { do_delete(); }, this); | 
					
						
							| 
									
										
										
										
											2020-02-10 19:39:30 +01:00
										 |  |  |     if (is_multi_line()) { | 
					
						
							|  |  |  |         m_go_to_line_action = Action::create( | 
					
						
							|  |  |  |             "Go to line...", { Mod_Ctrl, Key_L }, Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"), [this](auto&) { | 
					
						
							| 
									
										
										
										
											2020-07-16 07:54:42 -06:00
										 |  |  |                 String value; | 
					
						
							|  |  |  |                 if (InputBox::show(value, window(), "Line:", "Go to line") == InputBox::ExecOK) { | 
					
						
							|  |  |  |                     auto line_number = value.to_uint(); | 
					
						
							| 
									
										
										
										
											2020-06-12 21:07:52 +02:00
										 |  |  |                     if (line_number.has_value()) | 
					
						
							|  |  |  |                         set_cursor(line_number.value() - 1, 0); | 
					
						
							| 
									
										
										
										
											2020-02-10 19:39:30 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             this); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-03 20:54:38 +02:00
										 |  |  |     m_select_all_action = CommonActions::make_select_all_action([this](auto&) { select_all(); }, this); | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_text(const StringView& text) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-04-09 16:20:36 +02:00
										 |  |  |     m_selection.clear(); | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     document().set_text(text); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     update_content_size(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							| 
									
										
										
										
											2019-03-20 18:12:56 +01:00
										 |  |  |     if (is_single_line()) | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |         set_cursor(0, line(0).length()); | 
					
						
							| 
									
										
										
										
											2019-03-20 18:12:56 +01:00
										 |  |  |     else | 
					
						
							|  |  |  |         set_cursor(0, 0); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |     did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::update_content_size() | 
					
						
							| 
									
										
										
										
											2019-03-07 13:54:02 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     int content_width = 0; | 
					
						
							| 
									
										
										
										
											2019-08-28 23:26:24 -05:00
										 |  |  |     int content_height = 0; | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     for (auto& line : m_line_visual_data) { | 
					
						
							|  |  |  |         content_width = max(line.visual_rect.width(), content_width); | 
					
						
							|  |  |  |         content_height += line.visual_rect.height(); | 
					
						
							| 
									
										
										
										
											2019-08-28 23:26:24 -05:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-16 23:16:37 +01:00
										 |  |  |     content_width += m_horizontal_content_padding * 2; | 
					
						
							| 
									
										
										
										
											2019-04-24 23:42:49 +02:00
										 |  |  |     if (is_right_text_alignment(m_text_alignment)) | 
					
						
							|  |  |  |         content_width = max(frame_inner_rect().width(), content_width); | 
					
						
							| 
									
										
										
										
											2019-08-28 23:26:24 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     set_content_size({ content_width, content_height }); | 
					
						
							|  |  |  |     set_size_occupied_by_fixed_elements({ ruler_width(), 0 }); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | TextPosition TextEditor::text_position_at(const Gfx::IntPoint& a_position) const | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  |     auto position = a_position; | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     position.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value()); | 
					
						
							| 
									
										
										
										
											2019-03-16 23:16:37 +01:00
										 |  |  |     position.move_by(-(m_horizontal_content_padding + ruler_width()), 0); | 
					
						
							| 
									
										
										
										
											2019-04-25 02:15:45 +02:00
										 |  |  |     position.move_by(-frame_thickness(), -frame_thickness()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  |     if (is_single_line() && icon()) | 
					
						
							|  |  |  |         position.move_by(-(icon_size() + icon_padding()), 0); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t line_index = 0; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (is_line_wrapping_enabled()) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         for (size_t i = 0; i < line_count(); ++i) { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |             auto& rect = m_line_visual_data[i].visual_rect; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |             if (position.y() >= rect.top() && position.y() <= rect.bottom()) { | 
					
						
							|  |  |  |                 line_index = i; | 
					
						
							|  |  |  |                 break; | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             if (position.y() > rect.bottom()) | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |                 line_index = line_count() - 1; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         line_index = (size_t)(position.y() / line_height()); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     line_index = max((size_t)0, min(line_index, line_count() - 1)); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |     size_t column_index = 0; | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  |     switch (m_text_alignment) { | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |     case Gfx::TextAlignment::CenterLeft: | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         for_each_visual_line(line_index, [&](const Gfx::IntRect& rect, auto& view, size_t start_of_line) { | 
					
						
							| 
									
										
										
										
											2020-05-18 17:55:21 +02:00
										 |  |  |             if (is_multi_line() && !rect.contains_vertically(position.y())) | 
					
						
							|  |  |  |                 return IterationDecision::Continue; | 
					
						
							|  |  |  |             column_index = start_of_line; | 
					
						
							|  |  |  |             if (position.x() <= 0) { | 
					
						
							|  |  |  |                 // We're outside the text on the left side, put cursor at column 0 on this visual line.
 | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 int glyph_x = 0; | 
					
						
							|  |  |  |                 size_t i = 0; | 
					
						
							|  |  |  |                 for (; i < view.length(); ++i) { | 
					
						
							|  |  |  |                     int advance = font().glyph_width(view.codepoints()[i]) + font().glyph_spacing(); | 
					
						
							|  |  |  |                     if ((glyph_x + (advance / 2)) >= position.x()) | 
					
						
							|  |  |  |                         break; | 
					
						
							|  |  |  |                     glyph_x += advance; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-05-18 17:55:21 +02:00
										 |  |  |                 column_index += i; | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-05-18 17:55:21 +02:00
										 |  |  |             return IterationDecision::Break; | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  |         break; | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |     case Gfx::TextAlignment::CenterRight: | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         // FIXME: Support right-aligned line wrapping, I guess.
 | 
					
						
							|  |  |  |         ASSERT(!is_line_wrapping_enabled()); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |         column_index = (position.x() - content_x_for_position({ line_index, 0 }) + fixed_glyph_width() / 2) / fixed_glyph_width(); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         ASSERT_NOT_REACHED(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     column_index = max((size_t)0, min(column_index, line(line_index).length())); | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  |     return { line_index, column_index }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::doubleclick_event(MouseEvent& event) | 
					
						
							| 
									
										
										
										
											2019-04-25 22:29:25 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     if (event.button() != MouseButton::Left) | 
					
						
							| 
									
										
										
										
											2019-04-25 22:29:25 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (is_displayonly()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |     // NOTE: This ensures that spans are updated before we look at them.
 | 
					
						
							|  |  |  |     flush_pending_change_notification_if_needed(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  |     m_triple_click_timer.start(); | 
					
						
							| 
									
										
										
										
											2019-04-25 22:29:25 +02:00
										 |  |  |     m_in_drag_select = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto start = text_position_at(event.position()); | 
					
						
							|  |  |  |     auto end = start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |     if (!document().has_spans()) { | 
					
						
							| 
									
										
										
										
											2020-05-24 13:32:08 -04:00
										 |  |  |         start = document().first_word_break_before(start, false); | 
					
						
							| 
									
										
										
										
											2020-05-23 18:41:34 -04:00
										 |  |  |         end = document().first_word_break_after(end); | 
					
						
							| 
									
										
										
										
											2019-10-27 11:10:32 +01:00
										 |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |         for (auto& span : document().spans()) { | 
					
						
							| 
									
										
										
										
											2019-10-27 11:10:32 +01:00
										 |  |  |             if (!span.range.contains(start)) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             start = span.range.start(); | 
					
						
							|  |  |  |             end = span.range.end(); | 
					
						
							|  |  |  |             end.set_column(end.column() + 1); | 
					
						
							| 
									
										
										
										
											2019-04-25 22:29:25 +02:00
										 |  |  |             break; | 
					
						
							| 
									
										
										
										
											2019-10-27 11:10:32 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-04-25 22:29:25 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_selection.set(start, end); | 
					
						
							|  |  |  |     set_cursor(end); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  |     did_update_selection(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::mousedown_event(MouseEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     if (event.button() != MouseButton::Left) { | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (on_mousedown) | 
					
						
							|  |  |  |         on_mousedown(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (is_displayonly()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  |     if (m_triple_click_timer.is_valid() && m_triple_click_timer.elapsed() < 250) { | 
					
						
							| 
									
										
										
										
											2020-02-02 12:34:39 +01:00
										 |  |  |         m_triple_click_timer = Core::ElapsedTimer(); | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |         TextPosition start; | 
					
						
							|  |  |  |         TextPosition end; | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (is_multi_line()) { | 
					
						
							|  |  |  |             // select *current* line
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |             start = TextPosition(m_cursor.line(), 0); | 
					
						
							|  |  |  |             end = TextPosition(m_cursor.line(), line(m_cursor.line()).length()); | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             // select *whole* line
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |             start = TextPosition(0, 0); | 
					
						
							|  |  |  |             end = TextPosition(line_count() - 1, line(line_count() - 1).length()); | 
					
						
							| 
									
										
										
										
											2019-05-16 00:40:55 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         m_selection.set(start, end); | 
					
						
							|  |  |  |         set_cursor(end); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |     if (event.modifiers() & Mod_Shift) { | 
					
						
							|  |  |  |         if (!has_selection()) | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |             m_selection.set(m_cursor, {}); | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |     } else { | 
					
						
							|  |  |  |         m_selection.clear(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |     m_in_drag_select = true; | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  |     m_automatic_selection_scroll_timer->start(); | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |     set_cursor(text_position_at(event.position())); | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  |     if (!(event.modifiers() & Mod_Shift)) { | 
					
						
							|  |  |  |         if (!has_selection()) | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |             m_selection.set(m_cursor, {}); | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-05-16 00:15:58 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (m_selection.start().is_valid() && m_selection.start() != m_cursor) | 
					
						
							|  |  |  |         m_selection.set_end(m_cursor); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // FIXME: Only update the relevant rects.
 | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  |     did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::mouseup_event(MouseEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     if (event.button() == MouseButton::Left) { | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |         if (m_in_drag_select) { | 
					
						
							|  |  |  |             m_in_drag_select = false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::mousemove_event(MouseEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  |     m_last_mousemove_position = event.position(); | 
					
						
							| 
									
										
										
										
											2020-04-24 19:14:22 +02:00
										 |  |  |     if (m_in_drag_select && (rect().contains(event.position()) || !m_automatic_selection_scroll_timer->is_active())) { | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |         set_cursor(text_position_at(event.position())); | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |         m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |         did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         update(); | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  | void TextEditor::automatic_selection_scroll_timer_fired() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_in_drag_select) { | 
					
						
							|  |  |  |         m_automatic_selection_scroll_timer->stop(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     set_cursor(text_position_at(m_last_mousemove_position)); | 
					
						
							|  |  |  |     m_selection.set_end(m_cursor); | 
					
						
							|  |  |  |     did_update_selection(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | int TextEditor::ruler_width() const | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |     if (!m_ruler_visible) | 
					
						
							|  |  |  |         return 0; | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  |     // FIXME: Resize based on needed space.
 | 
					
						
							|  |  |  |     return 5 * font().glyph_width('x') + 4; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::ruler_content_rect(size_t line_index) const | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |     if (!m_ruler_visible) | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         return {}; | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2019-03-16 23:16:37 +01:00
										 |  |  |         0 - ruler_width() + horizontal_scrollbar().value(), | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |         line_content_rect(line_index).y(), | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  |         ruler_width(), | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |         line_content_rect(line_index).height() | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::ruler_rect_in_inner_coordinates() const | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     return { 0, 0, ruler_width(), height() - height_occupied_by_horizontal_scrollbar() }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::visible_text_rect_in_inner_coordinates() const | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2019-09-01 20:04:25 +02:00
										 |  |  |         m_horizontal_content_padding + (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + 1) : 0), | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |         0, | 
					
						
							| 
									
										
										
										
											2019-09-01 20:04:25 +02:00
										 |  |  |         frame_inner_rect().width() - (m_horizontal_content_padding * 2) - width_occupied_by_vertical_scrollbar() - ruler_width(), | 
					
						
							|  |  |  |         frame_inner_rect().height() - height_occupied_by_horizontal_scrollbar() | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::paint_event(PaintEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-04-28 13:15:53 +02:00
										 |  |  |     Color widget_background_color = palette().color(is_enabled() ? background_role() : Gfx::ColorRole::Window); | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |     // NOTE: This ensures that spans are updated before we look at them.
 | 
					
						
							|  |  |  |     flush_pending_change_notification_if_needed(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     Frame::paint_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-28 16:14:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     Painter painter(*this); | 
					
						
							| 
									
										
										
										
											2019-03-29 15:01:54 +01:00
										 |  |  |     painter.add_clip_rect(widget_inner_rect()); | 
					
						
							|  |  |  |     painter.add_clip_rect(event.rect()); | 
					
						
							| 
									
										
										
										
											2019-12-24 20:57:54 +01:00
										 |  |  |     painter.fill_rect(event.rect(), widget_background_color); | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (is_displayonly() && (is_focused() || has_visible_list())) { | 
					
						
							|  |  |  |         widget_background_color = palette().selection(); | 
					
						
							|  |  |  |         Gfx::IntRect display_rect { | 
					
						
							|  |  |  |             widget_inner_rect().x() + 1, | 
					
						
							|  |  |  |             widget_inner_rect().y() + 1, | 
					
						
							|  |  |  |             widget_inner_rect().width() - button_padding(), | 
					
						
							|  |  |  |             widget_inner_rect().height() - 2 | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         painter.add_clip_rect(display_rect); | 
					
						
							|  |  |  |         painter.add_clip_rect(event.rect()); | 
					
						
							|  |  |  |         painter.fill_rect(event.rect(), widget_background_color); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-25 01:09:44 +02:00
										 |  |  |     painter.translate(frame_thickness(), frame_thickness()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |     auto ruler_rect = ruler_rect_in_inner_coordinates(); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (m_ruler_visible) { | 
					
						
							| 
									
										
										
										
											2020-02-19 11:11:37 +01:00
										 |  |  |         painter.fill_rect(ruler_rect, palette().ruler()); | 
					
						
							|  |  |  |         painter.draw_line(ruler_rect.top_right(), ruler_rect.bottom_right(), palette().ruler_border()); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-16 16:54:51 +01:00
										 |  |  |     painter.translate(-horizontal_scrollbar().value(), -vertical_scrollbar().value()); | 
					
						
							| 
									
										
										
										
											2019-03-19 02:45:23 +01:00
										 |  |  |     if (m_ruler_visible) | 
					
						
							|  |  |  |         painter.translate(ruler_width(), 0); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t first_visible_line = text_position_at(event.rect().top_left()).line(); | 
					
						
							|  |  |  |     size_t last_visible_line = text_position_at(event.rect().bottom_right()).line(); | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     auto selection = normalized_selection(); | 
					
						
							|  |  |  |     bool has_selection = selection.is_valid(); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |     if (m_ruler_visible) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         for (size_t i = first_visible_line; i <= last_visible_line; ++i) { | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |             bool is_current_line = i == m_cursor.line(); | 
					
						
							|  |  |  |             auto ruler_line_rect = ruler_content_rect(i); | 
					
						
							|  |  |  |             painter.draw_text( | 
					
						
							| 
									
										
										
										
											2019-09-06 19:24:16 +02:00
										 |  |  |                 ruler_line_rect.shrunken(2, 0).translated(0, m_line_spacing / 2), | 
					
						
							| 
									
										
										
										
											2019-07-27 21:20:38 +02:00
										 |  |  |                 String::number(i + 1), | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |                 is_current_line ? Gfx::Font::default_bold_font() : font(), | 
					
						
							|  |  |  |                 Gfx::TextAlignment::TopRight, | 
					
						
							| 
									
										
										
										
											2020-02-19 11:11:37 +01:00
										 |  |  |                 is_current_line ? palette().ruler_active_text() : palette().ruler_inactive_text()); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:54:05 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |     Gfx::IntRect text_clip_rect { | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |         (m_ruler_visible ? (ruler_rect_in_inner_coordinates().right() + frame_thickness() + 1) : frame_thickness()), | 
					
						
							|  |  |  |         frame_thickness(), | 
					
						
							|  |  |  |         width() - width_occupied_by_vertical_scrollbar() - ruler_width(), | 
					
						
							|  |  |  |         height() - height_occupied_by_horizontal_scrollbar() | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-06-06 19:35:07 +02:00
										 |  |  |     if (m_ruler_visible) | 
					
						
							|  |  |  |         text_clip_rect.move_by(-ruler_width(), 0); | 
					
						
							| 
									
										
										
										
											2020-06-07 18:02:26 +02:00
										 |  |  |     text_clip_rect.move_by(horizontal_scrollbar().value(), vertical_scrollbar().value()); | 
					
						
							| 
									
										
										
										
											2019-08-25 07:14:02 +02:00
										 |  |  |     painter.add_clip_rect(text_clip_rect); | 
					
						
							| 
									
										
										
										
											2019-03-07 20:05:05 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t line_index = first_visible_line; line_index <= last_visible_line; ++line_index) { | 
					
						
							|  |  |  |         auto& line = this->line(line_index); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         bool physical_line_has_selection = has_selection && line_index >= selection.start().line() && line_index <= selection.end().line(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         size_t first_visual_line_with_selection = 0; | 
					
						
							|  |  |  |         size_t last_visual_line_with_selection = 0; | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |         if (physical_line_has_selection) { | 
					
						
							| 
									
										
										
										
											2019-08-25 14:04:46 +02:00
										 |  |  |             if (selection.start().line() < line_index) | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |                 first_visual_line_with_selection = 0; | 
					
						
							| 
									
										
										
										
											2019-08-25 14:04:46 +02:00
										 |  |  |             else | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |                 first_visual_line_with_selection = visual_line_containing(line_index, selection.start().column()); | 
					
						
							| 
									
										
										
										
											2019-08-25 14:04:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (selection.end().line() > line_index) | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |                 last_visual_line_with_selection = m_line_visual_data[line_index].visual_line_breaks.size(); | 
					
						
							| 
									
										
										
										
											2019-08-25 14:04:46 +02:00
										 |  |  |             else | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |                 last_visual_line_with_selection = visual_line_containing(line_index, selection.end().column()); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         size_t selection_start_column_within_line = selection.start().line() == line_index ? selection.start().column() : 0; | 
					
						
							|  |  |  |         size_t selection_end_column_within_line = selection.end().line() == line_index ? selection.end().column() : line.length(); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         size_t visual_line_index = 0; | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         for_each_visual_line(line_index, [&](const Gfx::IntRect& visual_line_rect, auto& visual_line_text, size_t start_of_visual_line) { | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |             if (is_multi_line() && line_index == m_cursor.line()) | 
					
						
							| 
									
										
										
										
											2019-12-24 20:57:54 +01:00
										 |  |  |                 painter.fill_rect(visual_line_rect, widget_background_color.darkened(0.9f)); | 
					
						
							| 
									
										
										
										
											2020-03-19 23:09:15 +01:00
										 |  |  | #ifdef DEBUG_TEXTEDITOR
 | 
					
						
							| 
									
										
										
										
											2019-09-16 20:57:32 +02:00
										 |  |  |             painter.draw_rect(visual_line_rect, Color::Cyan); | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |             if (!document().has_spans()) { | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                 // Fast-path for plain text
 | 
					
						
							| 
									
										
										
										
											2020-03-04 21:19:35 +01:00
										 |  |  |                 auto color = palette().color(is_enabled() ? foreground_role() : Gfx::ColorRole::DisabledText); | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |                 if (is_displayonly() && (is_focused() || has_visible_list())) | 
					
						
							|  |  |  |                     color = palette().color(is_enabled() ? Gfx::ColorRole::SelectionText : Gfx::ColorRole::DisabledText); | 
					
						
							| 
									
										
										
										
											2020-03-04 21:19:35 +01:00
										 |  |  |                 painter.draw_text(visual_line_rect, visual_line_text, m_text_alignment, color); | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |                 Gfx::IntRect character_rect = { visual_line_rect.location(), { 0, line_height() } }; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |                 for (size_t i = 0; i < visual_line_text.length(); ++i) { | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |                     u32 codepoint = visual_line_text.substring_view(i, 1).codepoints()[0]; | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |                     const Gfx::Font* font = &this->font(); | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                     Color color; | 
					
						
							| 
									
										
										
										
											2019-11-18 19:10:06 +01:00
										 |  |  |                     Optional<Color> background_color; | 
					
						
							| 
									
										
										
										
											2020-03-12 16:36:25 +02:00
										 |  |  |                     bool underline = false; | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |                     TextPosition physical_position(line_index, start_of_visual_line + i); | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                     // FIXME: This is *horribly* inefficient.
 | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |                     for (auto& span : document().spans()) { | 
					
						
							| 
									
										
										
										
											2019-10-26 15:32:12 +02:00
										 |  |  |                         if (!span.range.contains(physical_position)) | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                             continue; | 
					
						
							|  |  |  |                         color = span.color; | 
					
						
							| 
									
										
										
										
											2019-10-26 00:13:07 +02:00
										 |  |  |                         if (span.font) | 
					
						
							|  |  |  |                             font = span.font; | 
					
						
							| 
									
										
										
										
											2019-11-18 19:10:06 +01:00
										 |  |  |                         background_color = span.background_color; | 
					
						
							| 
									
										
										
										
											2020-03-12 16:36:25 +02:00
										 |  |  |                         underline = span.is_underlined; | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                         break; | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |                     character_rect.set_width(font->glyph_width(codepoint) + font->glyph_spacing()); | 
					
						
							| 
									
										
										
										
											2019-11-18 19:10:06 +01:00
										 |  |  |                     if (background_color.has_value()) | 
					
						
							|  |  |  |                         painter.fill_rect(character_rect, background_color.value()); | 
					
						
							| 
									
										
										
										
											2019-10-26 00:13:07 +02:00
										 |  |  |                     painter.draw_text(character_rect, visual_line_text.substring_view(i, 1), *font, m_text_alignment, color); | 
					
						
							| 
									
										
										
										
											2020-03-12 16:36:25 +02:00
										 |  |  |                     if (underline) { | 
					
						
							|  |  |  |                         painter.draw_line(character_rect.bottom_left().translated(0, 1), character_rect.bottom_right().translated(0, 1), color); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |                     character_rect.move_by(character_rect.width(), 0); | 
					
						
							| 
									
										
										
										
											2019-10-25 21:05:06 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |             if (physical_line_has_selection) { | 
					
						
							| 
									
										
										
										
											2020-04-24 20:01:57 +02:00
										 |  |  |                 size_t start_of_selection_within_visual_line = (size_t)max(0, (int)selection_start_column_within_line - (int)start_of_visual_line); | 
					
						
							|  |  |  |                 size_t end_of_selection_within_visual_line = selection_end_column_within_line - start_of_visual_line; | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-24 20:01:57 +02:00
										 |  |  |                 bool current_visual_line_has_selection = start_of_selection_within_visual_line != end_of_selection_within_visual_line | 
					
						
							|  |  |  |                     && ((line_index != selection.start().line() && line_index != selection.end().line()) | 
					
						
							|  |  |  |                         || (visual_line_index >= first_visual_line_with_selection && visual_line_index <= last_visual_line_with_selection)); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |                 if (current_visual_line_has_selection) { | 
					
						
							|  |  |  |                     bool selection_begins_on_current_visual_line = visual_line_index == first_visual_line_with_selection; | 
					
						
							|  |  |  |                     bool selection_ends_on_current_visual_line = visual_line_index == last_visual_line_with_selection; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     int selection_left = selection_begins_on_current_visual_line | 
					
						
							| 
									
										
										
										
											2019-12-17 21:15:10 +01:00
										 |  |  |                         ? content_x_for_position({ line_index, (size_t)selection_start_column_within_line }) | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |                         : m_horizontal_content_padding; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     int selection_right = selection_ends_on_current_visual_line | 
					
						
							| 
									
										
										
										
											2019-12-17 21:15:10 +01:00
										 |  |  |                         ? content_x_for_position({ line_index, (size_t)selection_end_column_within_line }) | 
					
						
							| 
									
										
										
										
											2019-09-01 17:30:23 +02:00
										 |  |  |                         : visual_line_rect.right() + 1; | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |                     Gfx::IntRect selection_rect { | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |                         selection_left, | 
					
						
							|  |  |  |                         visual_line_rect.y(), | 
					
						
							|  |  |  |                         selection_right - selection_left, | 
					
						
							|  |  |  |                         visual_line_rect.height() | 
					
						
							|  |  |  |                     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-15 16:26:52 +01:00
										 |  |  |                     Color background_color = is_focused() ? palette().selection() : palette().inactive_selection(); | 
					
						
							|  |  |  |                     Color text_color = is_focused() ? palette().selection_text() : palette().inactive_selection_text(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     painter.fill_rect(selection_rect, background_color); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 14:10:36 -04:00
										 |  |  |                     if (visual_line_text.codepoints()) { | 
					
						
							|  |  |  |                         Utf32View visual_selected_text { | 
					
						
							|  |  |  |                             visual_line_text.codepoints() + start_of_selection_within_visual_line, | 
					
						
							|  |  |  |                             end_of_selection_within_visual_line - start_of_selection_within_visual_line | 
					
						
							|  |  |  |                         }; | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-29 14:10:36 -04:00
										 |  |  |                         painter.draw_text(selection_rect, visual_selected_text, Gfx::TextAlignment::CenterLeft, text_color); | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |             ++visual_line_index; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |             return IterationDecision::Continue; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  |     if (!is_multi_line() && m_icon) { | 
					
						
							|  |  |  |         Gfx::IntRect icon_rect { icon_padding(), 1, icon_size(), icon_size() }; | 
					
						
							|  |  |  |         painter.draw_scaled_bitmap(icon_rect, *m_icon, m_icon->rect()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (is_focused() && m_cursor_state && !is_displayonly()) | 
					
						
							| 
									
										
										
										
											2020-02-20 09:01:48 +01:00
										 |  |  |         painter.fill_rect(cursor_content_rect(), palette().text_cursor()); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::toggle_selection_if_needed_for_event(const KeyEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     if (event.shift() && !m_selection.is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         m_selection.set(m_cursor, {}); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |         did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         update(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     if (!event.shift() && m_selection.is_valid()) { | 
					
						
							|  |  |  |         m_selection.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |         did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         update(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::select_all() | 
					
						
							| 
									
										
										
										
											2019-06-22 10:38:38 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     TextPosition start_of_document { 0, 0 }; | 
					
						
							|  |  |  |     TextPosition end_of_document { line_count() - 1, line(line_count() - 1).length() }; | 
					
						
							| 
									
										
										
										
											2020-05-27 18:41:54 +02:00
										 |  |  |     m_selection.set(end_of_document, start_of_document); | 
					
						
							| 
									
										
										
										
											2019-06-22 10:38:38 +02:00
										 |  |  |     did_update_selection(); | 
					
						
							| 
									
										
										
										
											2020-05-27 18:41:54 +02:00
										 |  |  |     set_cursor(start_of_document); | 
					
						
							| 
									
										
										
										
											2019-06-22 10:38:38 +02:00
										 |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::get_selection_line_boundaries(size_t& first_line, size_t& last_line) | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     auto selection = normalized_selection(); | 
					
						
							|  |  |  |     if (!selection.is_valid()) { | 
					
						
							|  |  |  |         first_line = m_cursor.line(); | 
					
						
							|  |  |  |         last_line = m_cursor.line(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     first_line = selection.start().line(); | 
					
						
							|  |  |  |     last_line = selection.end().line(); | 
					
						
							|  |  |  |     if (first_line != last_line && selection.end().column() == 0) | 
					
						
							|  |  |  |         last_line -= 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::move_selected_lines_up() | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t first_line; | 
					
						
							|  |  |  |     size_t last_line; | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |     get_selection_line_boundaries(first_line, last_line); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (first_line == 0) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto& lines = document().lines(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     lines.insert((int)last_line, lines.take((int)first_line - 1)); | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |     m_cursor = { first_line - 1, 0 }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (has_selection()) { | 
					
						
							|  |  |  |         m_selection.set_start({ first_line - 1, 0 }); | 
					
						
							|  |  |  |         m_selection.set_end({ last_line - 1, line(last_line - 1).length() }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     did_change(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::move_selected_lines_down() | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t first_line; | 
					
						
							|  |  |  |     size_t last_line; | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |     get_selection_line_boundaries(first_line, last_line); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto& lines = document().lines(); | 
					
						
							| 
									
										
										
										
											2020-03-01 12:35:09 +01:00
										 |  |  |     ASSERT(lines.size() != 0); | 
					
						
							|  |  |  |     if (last_line >= lines.size() - 1) | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     lines.insert((int)first_line, lines.take((int)last_line + 1)); | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |     m_cursor = { first_line + 1, 0 }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (has_selection()) { | 
					
						
							|  |  |  |         m_selection.set_start({ first_line + 1, 0 }); | 
					
						
							|  |  |  |         m_selection.set_end({ last_line + 1, line(last_line + 1).length() }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     did_change(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  | int strcmp_utf32(const u32* s1, const u32* s2, size_t n) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     while (n-- > 0) { | 
					
						
							|  |  |  |         if (*s1++ != *s2++) | 
					
						
							|  |  |  |             return s1[-1] < s2[-1] ? -1 : 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::sort_selected_lines() | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!has_selection()) | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t first_line; | 
					
						
							|  |  |  |     size_t last_line; | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  |     get_selection_line_boundaries(first_line, last_line); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto& lines = document().lines(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     auto start = lines.begin() + (int)first_line; | 
					
						
							|  |  |  |     auto end = lines.begin() + (int)last_line + 1; | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     quick_sort(start, end, [](auto& a, auto& b) { | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  |         return strcmp_utf32(a.codepoints(), b.codepoints(), min(a.length(), b.length())) < 0; | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     did_change(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::keydown_event(KeyEvent& event) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-05-15 02:39:58 +02:00
										 |  |  |     if (is_single_line() && event.key() == KeyCode::Key_Tab) | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |         return Widget::keydown_event(event); | 
					
						
							| 
									
										
										
										
											2019-05-15 02:39:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-23 07:53:58 +02:00
										 |  |  |     if (is_single_line() && event.key() == KeyCode::Key_Return) { | 
					
						
							|  |  |  |         if (on_return_pressed) | 
					
						
							|  |  |  |             on_return_pressed(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-19 01:41:00 +01:00
										 |  |  |     if (event.key() == KeyCode::Key_Escape) { | 
					
						
							|  |  |  |         if (on_escape_pressed) | 
					
						
							| 
									
										
										
										
											2019-04-10 03:08:29 +02:00
										 |  |  |             on_escape_pressed(); | 
					
						
							| 
									
										
										
										
											2019-03-19 01:41:00 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-24 10:20:25 +01:00
										 |  |  |     if (is_multi_line() && event.key() == KeyCode::Key_Up) { | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         if (m_cursor.line() > 0) { | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |             if (event.ctrl() && event.shift()) { | 
					
						
							|  |  |  |                 move_selected_lines_up(); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             size_t new_line = m_cursor.line() - 1; | 
					
						
							|  |  |  |             size_t new_column = min(m_cursor.column(), line(new_line).length()); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  |             set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-07-14 17:18:12 -04:00
										 |  |  |     } else if (event.key() == KeyCode::Key_Up) { | 
					
						
							|  |  |  |         if (on_up_pressed) | 
					
						
							|  |  |  |             on_up_pressed(); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-24 10:20:25 +01:00
										 |  |  |     if (is_multi_line() && event.key() == KeyCode::Key_Down) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         if (m_cursor.line() < (line_count() - 1)) { | 
					
						
							| 
									
										
										
										
											2019-11-08 20:14:42 +01:00
										 |  |  |             if (event.ctrl() && event.shift()) { | 
					
						
							|  |  |  |                 move_selected_lines_down(); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             size_t new_line = m_cursor.line() + 1; | 
					
						
							|  |  |  |             size_t new_column = min(m_cursor.column(), line(new_line).length()); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  |             set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-07-14 17:18:12 -04:00
										 |  |  |     } else if (event.key() == KeyCode::Key_Down) { | 
					
						
							|  |  |  |         if (on_down_pressed) | 
					
						
							|  |  |  |             on_down_pressed(); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-24 10:20:25 +01:00
										 |  |  |     if (is_multi_line() && event.key() == KeyCode::Key_PageUp) { | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |         if (m_cursor.line() > 0) { | 
					
						
							| 
									
										
										
										
											2020-01-27 22:58:50 +01:00
										 |  |  |             size_t page_step = (size_t)visible_content_rect().height() / (size_t)line_height(); | 
					
						
							|  |  |  |             size_t new_line = m_cursor.line() < page_step ? 0 : m_cursor.line() - page_step; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             size_t new_column = min(m_cursor.column(), line(new_line).length()); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |             set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-07-14 17:18:12 -04:00
										 |  |  |     } else if (event.key() == KeyCode::Key_PageUp) { | 
					
						
							|  |  |  |         if (on_pageup_pressed) | 
					
						
							|  |  |  |             on_pageup_pressed(); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-02-24 10:20:25 +01:00
										 |  |  |     if (is_multi_line() && event.key() == KeyCode::Key_PageDown) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         if (m_cursor.line() < (line_count() - 1)) { | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |             int new_line = min(line_count() - 1, m_cursor.line() + visible_content_rect().height() / line_height()); | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |             int new_column = min(m_cursor.column(), lines()[new_line].length()); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |             set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-07-14 17:18:12 -04:00
										 |  |  |     } else if (event.key() == KeyCode::Key_PageDown) { | 
					
						
							|  |  |  |         if (on_pagedown_pressed) | 
					
						
							|  |  |  |             on_pagedown_pressed(); | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-07 17:11:17 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |     if (event.key() == KeyCode::Key_Left) { | 
					
						
							| 
									
										
										
										
											2020-05-23 18:41:34 -04:00
										 |  |  |         if (event.ctrl()) { | 
					
						
							|  |  |  |             TextPosition new_cursor; | 
					
						
							|  |  |  |             if (document().has_spans()) { | 
					
						
							|  |  |  |                 auto span = document().first_non_skippable_span_before(m_cursor); | 
					
						
							|  |  |  |                 if (span.has_value()) { | 
					
						
							|  |  |  |                     new_cursor = span.value().range.start(); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     // No remaining spans, just use word break calculation
 | 
					
						
							| 
									
										
										
										
											2020-05-24 13:32:08 -04:00
										 |  |  |                     new_cursor = document().first_word_break_before(m_cursor, true); | 
					
						
							| 
									
										
										
										
											2020-05-23 18:41:34 -04:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2020-05-24 13:32:08 -04:00
										 |  |  |                 new_cursor = document().first_word_break_before(m_cursor, true); | 
					
						
							| 
									
										
										
										
											2020-05-23 18:41:34 -04:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-11-15 20:36:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							|  |  |  |             set_cursor(new_cursor); | 
					
						
							|  |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							|  |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							|  |  |  |                 did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-11-15 20:17:49 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         if (m_cursor.column() > 0) { | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  |             int new_column = m_cursor.column() - 1; | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  |             set_cursor(m_cursor.line(), new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-09 22:02:35 +01:00
										 |  |  |         } else if (m_cursor.line() > 0) { | 
					
						
							|  |  |  |             int new_line = m_cursor.line() - 1; | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |             int new_column = lines()[new_line].length(); | 
					
						
							| 
									
										
										
										
											2019-03-09 22:02:35 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							|  |  |  |             set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-09 22:02:35 +01:00
										 |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |                 did_update_selection(); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |     if (event.key() == KeyCode::Key_Right) { | 
					
						
							| 
									
										
										
										
											2020-05-23 18:41:34 -04:00
										 |  |  |         if (event.ctrl()) { | 
					
						
							|  |  |  |             TextPosition new_cursor; | 
					
						
							|  |  |  |             if (document().has_spans()) { | 
					
						
							|  |  |  |                 auto span = document().first_non_skippable_span_after(m_cursor); | 
					
						
							|  |  |  |                 if (span.has_value()) { | 
					
						
							|  |  |  |                     new_cursor = span.value().range.start(); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     // No remaining spans, just use word break calculation
 | 
					
						
							|  |  |  |                     new_cursor = document().first_word_break_after(m_cursor); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 new_cursor = document().first_word_break_after(m_cursor); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2019-11-15 20:36:45 +01:00
										 |  |  |             toggle_selection_if_needed_for_event(event); | 
					
						
							|  |  |  |             set_cursor(new_cursor); | 
					
						
							|  |  |  |             if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							|  |  |  |                 m_selection.set_end(m_cursor); | 
					
						
							|  |  |  |                 did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-11-15 20:17:49 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-08-25 22:06:19 -06:00
										 |  |  |         int new_line = m_cursor.line(); | 
					
						
							|  |  |  |         int new_column = m_cursor.column(); | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         if (m_cursor.column() < current_line().length()) { | 
					
						
							| 
									
										
										
										
											2019-08-25 22:06:19 -06:00
										 |  |  |             new_line = m_cursor.line(); | 
					
						
							|  |  |  |             new_column = m_cursor.column() + 1; | 
					
						
							| 
									
										
										
										
											2019-03-09 22:02:35 +01:00
										 |  |  |         } else if (m_cursor.line() != line_count() - 1) { | 
					
						
							| 
									
										
										
										
											2019-08-25 22:06:19 -06:00
										 |  |  |             new_line = m_cursor.line() + 1; | 
					
						
							|  |  |  |             new_column = 0; | 
					
						
							| 
									
										
										
										
											2019-09-14 22:10:44 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-08-25 22:06:19 -06:00
										 |  |  |         toggle_selection_if_needed_for_event(event); | 
					
						
							|  |  |  |         set_cursor(new_line, new_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |         if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-08-25 22:06:19 -06:00
										 |  |  |             m_selection.set_end(m_cursor); | 
					
						
							|  |  |  |             did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-09-14 22:10:44 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  |     if (!event.ctrl() && event.key() == KeyCode::Key_Home) { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         size_t first_nonspace_column = current_line().first_non_whitespace_column(); | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-10-26 13:57:51 +02:00
										 |  |  |         if (m_cursor.column() == first_nonspace_column) | 
					
						
							|  |  |  |             set_cursor(m_cursor.line(), 0); | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |             set_cursor(m_cursor.line(), first_nonspace_column); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |         if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |             m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |             did_update_selection(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  |     if (!event.ctrl() && event.key() == KeyCode::Key_End) { | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         set_cursor(m_cursor.line(), current_line().length()); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |         if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |             m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |             did_update_selection(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  |     if (event.ctrl() && event.key() == KeyCode::Key_Home) { | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         set_cursor(0, 0); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |         if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |             m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |             did_update_selection(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  |     if (event.ctrl() && event.key() == KeyCode::Key_End) { | 
					
						
							| 
									
										
										
										
											2019-03-08 00:49:45 +01:00
										 |  |  |         toggle_selection_if_needed_for_event(event); | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |         set_cursor(line_count() - 1, lines()[line_count() - 1].length()); | 
					
						
							| 
									
										
										
										
											2019-10-26 12:57:05 +02:00
										 |  |  |         if (event.shift() && m_selection.start().is_valid()) { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |             m_selection.set_end(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |             did_update_selection(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 13:21:51 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-15 13:45:27 -06:00
										 |  |  |     if (event.alt() && event.shift() && event.key() == KeyCode::Key_S) { | 
					
						
							|  |  |  |         sort_selected_lines(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-08 18:50:14 +01:00
										 |  |  |     if (event.key() == KeyCode::Key_Backspace) { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |         if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-03-08 14:10:34 +01:00
										 |  |  |         if (has_selection()) { | 
					
						
							|  |  |  |             delete_selection(); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |             did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:10:34 +01:00
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 16:04:21 +01:00
										 |  |  |         if (m_cursor.column() > 0) { | 
					
						
							| 
									
										
										
										
											2019-10-27 10:42:48 +01:00
										 |  |  |             int erase_count = 1; | 
					
						
							| 
									
										
										
										
											2020-07-21 22:28:59 -07:00
										 |  |  |             if (event.modifiers() == Mod_Ctrl) { | 
					
						
							|  |  |  |                 auto word_break_pos = document().first_word_break_before(m_cursor, true); | 
					
						
							|  |  |  |                 erase_count = m_cursor.column() - word_break_pos.column(); | 
					
						
							|  |  |  |             } else if (current_line().first_non_whitespace_column() >= m_cursor.column()) { | 
					
						
							| 
									
										
										
										
											2019-10-27 10:42:48 +01:00
										 |  |  |                 int new_column; | 
					
						
							|  |  |  |                 if (m_cursor.column() % m_soft_tab_width == 0) | 
					
						
							|  |  |  |                     new_column = m_cursor.column() - m_soft_tab_width; | 
					
						
							|  |  |  |                 else | 
					
						
							|  |  |  |                     new_column = (m_cursor.column() / m_soft_tab_width) * m_soft_tab_width; | 
					
						
							|  |  |  |                 erase_count = m_cursor.column() - new_column; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-07 16:15:25 +01:00
										 |  |  |             // Backspace within line
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |             TextRange erased_range({ m_cursor.line(), m_cursor.column() - erase_count }, m_cursor); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |             auto erased_text = document().text_in_range(erased_range); | 
					
						
							|  |  |  |             execute<RemoveTextCommand>(erased_text, erased_range); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:18:22 +01:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-03-07 16:04:21 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 16:15:25 +01:00
										 |  |  |         if (m_cursor.column() == 0 && m_cursor.line() != 0) { | 
					
						
							| 
									
										
										
										
											2019-03-07 16:33:07 +01:00
										 |  |  |             // Backspace at column 0; merge with previous line
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             size_t previous_length = line(m_cursor.line() - 1).length(); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |             TextRange erased_range({ m_cursor.line() - 1, previous_length }, m_cursor); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |             execute<RemoveTextCommand>("\n", erased_range); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:18:22 +01:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-03-07 16:15:25 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-03-07 16:04:21 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  |     if (event.modifiers() == Mod_Shift && event.key() == KeyCode::Key_Delete) { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |         if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  |         delete_current_line(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-08 18:50:14 +01:00
										 |  |  |     if (event.key() == KeyCode::Key_Delete) { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |         if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |             return; | 
					
						
							| 
									
										
										
										
											2019-03-20 23:11:00 +01:00
										 |  |  |         do_delete(); | 
					
						
							| 
									
										
										
										
											2019-03-07 16:33:07 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     if (is_editable() && !event.ctrl() && !event.alt() && event.code_point() != 0) { | 
					
						
							| 
									
										
										
										
											2020-06-11 21:31:53 +03:00
										 |  |  |         StringBuilder sb; | 
					
						
							|  |  |  |         sb.append_codepoint(event.code_point()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         insert_at_cursor_or_replace_selection(sb.to_string()); | 
					
						
							| 
									
										
										
										
											2020-02-24 10:20:25 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     event.ignore(); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::delete_current_line() | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     if (has_selection()) | 
					
						
							|  |  |  |         return delete_selection(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     TextPosition start; | 
					
						
							|  |  |  |     TextPosition end; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     if (m_cursor.line() == 0 && line_count() == 1) { | 
					
						
							| 
									
										
										
										
											2019-11-30 17:19:35 +01:00
										 |  |  |         start = { 0, 0 }; | 
					
						
							|  |  |  |         end = { 0, line(0).length() }; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     } else if (m_cursor.line() == line_count() - 1) { | 
					
						
							| 
									
										
										
										
											2019-11-30 17:19:35 +01:00
										 |  |  |         start = { m_cursor.line() - 1, line(m_cursor.line()).length() }; | 
					
						
							|  |  |  |         end = { m_cursor.line(), line(m_cursor.line()).length() }; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         start = { m_cursor.line(), 0 }; | 
					
						
							|  |  |  |         end = { m_cursor.line() + 1, 0 }; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     TextRange erased_range(start, end); | 
					
						
							| 
									
										
										
										
											2019-11-30 17:19:35 +01:00
										 |  |  |     execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range); | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::do_delete() | 
					
						
							| 
									
										
										
										
											2019-03-20 23:11:00 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-25 13:13:46 +01:00
										 |  |  |     if (has_selection()) | 
					
						
							|  |  |  |         return delete_selection(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-20 23:11:00 +01:00
										 |  |  |     if (m_cursor.column() < current_line().length()) { | 
					
						
							|  |  |  |         // Delete within line
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |         TextRange erased_range(m_cursor, { m_cursor.line(), m_cursor.column() + 1 }); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:58:07 +01:00
										 |  |  |         execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range); | 
					
						
							| 
									
										
										
										
											2019-03-20 23:11:00 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (m_cursor.column() == current_line().length() && m_cursor.line() != line_count() - 1) { | 
					
						
							|  |  |  |         // Delete at end of line; merge with next line
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |         TextRange erased_range(m_cursor, { m_cursor.line() + 1, 0 }); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:58:07 +01:00
										 |  |  |         execute<RemoveTextCommand>(document().text_in_range(erased_range), erased_range); | 
					
						
							| 
									
										
										
										
											2019-03-20 23:11:00 +01:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | int TextEditor::content_x_for_position(const TextPosition& position) const | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     auto& line = this->line(position.line()); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |     int x_offset = 0; | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  |     switch (m_text_alignment) { | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |     case Gfx::TextAlignment::CenterLeft: | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         for_each_visual_line(position.line(), [&](const Gfx::IntRect&, auto& visual_line_view, size_t start_of_visual_line) { | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |             size_t offset_in_visual_line = position.column() - start_of_visual_line; | 
					
						
							|  |  |  |             if (position.column() >= start_of_visual_line && (offset_in_visual_line <= visual_line_view.length())) { | 
					
						
							|  |  |  |                 if (offset_in_visual_line == 0) { | 
					
						
							|  |  |  |                     x_offset = 0; | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     x_offset = font().width(visual_line_view.substring_view(0, offset_in_visual_line)); | 
					
						
							|  |  |  |                     x_offset += font().glyph_spacing(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2019-08-25 09:06:54 +02:00
										 |  |  |                 return IterationDecision::Break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return IterationDecision::Continue; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  |         return m_horizontal_content_padding + ((is_single_line() && icon()) ? (icon_size() + icon_padding()) : 0) + x_offset; | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  |     case Gfx::TextAlignment::CenterRight: | 
					
						
							| 
									
										
										
										
											2019-08-25 09:06:54 +02:00
										 |  |  |         // FIXME
 | 
					
						
							|  |  |  |         ASSERT(!is_line_wrapping_enabled()); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |         return content_width() - m_horizontal_content_padding - (line.length() * fixed_glyph_width()) + (position.column() * fixed_glyph_width()); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  |     default: | 
					
						
							|  |  |  |         ASSERT_NOT_REACHED(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::content_rect_for_position(const TextPosition& position) const | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  |     if (!position.is_valid()) | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         return {}; | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |     ASSERT(!lines().is_empty()); | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  |     ASSERT(position.column() <= (current_line().length() + 1)); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  |     int x = content_x_for_position(position); | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-03-28 15:30:29 +01:00
										 |  |  |     if (is_single_line()) { | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         Gfx::IntRect rect { x, 0, 1, font().glyph_height() + 2 }; | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  |         rect.center_vertically_within({ {}, frame_inner_rect().size() }); | 
					
						
							|  |  |  |         return rect; | 
					
						
							| 
									
										
										
										
											2019-03-28 15:30:29 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-25 09:06:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |     Gfx::IntRect rect; | 
					
						
							|  |  |  |     for_each_visual_line(position.line(), [&](const Gfx::IntRect& visual_line_rect, auto& view, size_t start_of_visual_line) { | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |         if (position.column() >= start_of_visual_line && ((position.column() - start_of_visual_line) <= view.length())) { | 
					
						
							| 
									
										
										
										
											2019-08-25 09:06:54 +02:00
										 |  |  |             // NOTE: We have to subtract the horizontal padding here since it's part of the visual line rect
 | 
					
						
							|  |  |  |             //       *and* included in what we get from content_x_for_position().
 | 
					
						
							|  |  |  |             rect = { | 
					
						
							|  |  |  |                 visual_line_rect.x() + x - (m_horizontal_content_padding), | 
					
						
							|  |  |  |                 visual_line_rect.y(), | 
					
						
							|  |  |  |                 1, | 
					
						
							|  |  |  |                 line_height() | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |             return IterationDecision::Break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return IterationDecision::Continue; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return rect; | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::cursor_content_rect() const | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     return content_rect_for_position(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::line_widget_rect(size_t line_index) const | 
					
						
							| 
									
										
										
										
											2019-03-07 02:24:46 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     auto rect = line_content_rect(line_index); | 
					
						
							| 
									
										
										
										
											2019-04-24 23:15:15 +02:00
										 |  |  |     rect.set_x(frame_thickness()); | 
					
						
							|  |  |  |     rect.set_width(frame_inner_rect().width()); | 
					
						
							|  |  |  |     rect.move_by(0, -(vertical_scrollbar().value())); | 
					
						
							| 
									
										
										
										
											2019-04-25 01:09:44 +02:00
										 |  |  |     rect.move_by(0, frame_thickness()); | 
					
						
							|  |  |  |     rect.intersect(frame_inner_rect()); | 
					
						
							| 
									
										
										
										
											2019-03-07 13:13:25 +01:00
										 |  |  |     return rect; | 
					
						
							| 
									
										
										
										
											2019-03-07 02:24:46 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::scroll_position_into_view(const TextPosition& position) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  |     auto rect = content_rect_for_position(position); | 
					
						
							|  |  |  |     if (position.column() == 0) | 
					
						
							|  |  |  |         rect.set_x(content_x_for_position({ position.line(), 0 }) - 2); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     else if (position.column() == line(position.line()).length()) | 
					
						
							|  |  |  |         rect.set_x(content_x_for_position({ position.line(), line(position.line()).length() }) + 2); | 
					
						
							| 
									
										
										
										
											2019-04-25 01:33:59 +02:00
										 |  |  |     scroll_into_view(rect, true, true); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::scroll_cursor_into_view() | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-06-07 21:03:29 +02:00
										 |  |  |     if (!m_reflow_deferred) | 
					
						
							|  |  |  |         scroll_position_into_view(m_cursor); | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  | Gfx::IntRect TextEditor::line_content_rect(size_t line_index) const | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     auto& line = this->line(line_index); | 
					
						
							| 
									
										
										
										
											2019-03-28 15:30:29 +01:00
										 |  |  |     if (is_single_line()) { | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         Gfx::IntRect line_rect = { content_x_for_position({ line_index, 0 }), 0, font().width(line.view()), font().glyph_height() + 4 }; | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         line_rect.center_vertically_within({ {}, frame_inner_rect().size() }); | 
					
						
							| 
									
										
										
										
											2019-03-28 15:30:29 +01:00
										 |  |  |         return line_rect; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     if (is_line_wrapping_enabled()) | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |         return m_line_visual_data[line_index].visual_rect; | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     return { | 
					
						
							| 
									
										
										
										
											2019-04-24 22:46:27 +02:00
										 |  |  |         content_x_for_position({ line_index, 0 }), | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         (int)line_index * line_height(), | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |         font().width(line.view()), | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  |         line_height() | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  |     }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::update_cursor() | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-07 13:23:17 +01:00
										 |  |  |     update(line_widget_rect(m_cursor.line())); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:31:06 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_cursor(size_t line, size_t column) | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  |     set_cursor({ line, column }); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_cursor(const TextPosition& a_position) | 
					
						
							| 
									
										
										
										
											2019-03-07 15:03:38 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |     ASSERT(!lines().is_empty()); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:58:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     TextPosition position = a_position; | 
					
						
							| 
									
										
										
										
											2019-10-21 18:58:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     if (position.line() >= line_count()) | 
					
						
							|  |  |  |         position.set_line(line_count() - 1); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:58:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |     if (position.column() > lines()[position.line()].length()) | 
					
						
							|  |  |  |         position.set_column(lines()[position.line()].length()); | 
					
						
							| 
									
										
										
										
											2019-10-21 18:58:27 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 00:31:46 +02:00
										 |  |  |     if (m_cursor != position && is_visual_data_up_to_date()) { | 
					
						
							| 
									
										
										
										
											2019-05-06 22:04:53 +02:00
										 |  |  |         // NOTE: If the old cursor is no longer valid, repaint everything just in case.
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         auto old_cursor_line_rect = m_cursor.line() < line_count() | 
					
						
							| 
									
										
										
										
											2019-05-06 22:04:53 +02:00
										 |  |  |             ? line_widget_rect(m_cursor.line()) | 
					
						
							|  |  |  |             : rect(); | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |         m_cursor = position; | 
					
						
							|  |  |  |         m_cursor_state = true; | 
					
						
							|  |  |  |         scroll_cursor_into_view(); | 
					
						
							|  |  |  |         update(old_cursor_line_rect); | 
					
						
							|  |  |  |         update_cursor(); | 
					
						
							| 
									
										
										
										
											2020-06-13 00:31:46 +02:00
										 |  |  |     } else if (m_cursor != position) { | 
					
						
							|  |  |  |         m_cursor = position; | 
					
						
							|  |  |  |         m_cursor_state = true; | 
					
						
							| 
									
										
										
										
											2019-03-08 17:53:02 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-18 19:10:06 +01:00
										 |  |  |     cursor_did_change(); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  |     if (on_cursor_change) | 
					
						
							| 
									
										
										
										
											2019-04-10 03:08:29 +02:00
										 |  |  |         on_cursor_change(); | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  |     if (m_highlighter) | 
					
						
							|  |  |  |         m_highlighter->cursor_did_change(); | 
					
						
							| 
									
										
										
										
											2019-03-07 00:46:29 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::focusin_event(Core::Event&) | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-05-21 17:37:28 +02:00
										 |  |  |     m_cursor_state = true; | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  |     update_cursor(); | 
					
						
							|  |  |  |     start_timer(500); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::focusout_event(Core::Event&) | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     stop_timer(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::timer_event(Core::TimerEvent&) | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_cursor_state = !m_cursor_state; | 
					
						
							| 
									
										
										
										
											2019-11-05 16:37:47 -06:00
										 |  |  |     if (is_focused()) | 
					
						
							| 
									
										
										
										
											2019-03-07 01:05:35 +01:00
										 |  |  |         update_cursor(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | bool TextEditor::write_to_file(const StringView& path) | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-07-08 20:01:49 +02:00
										 |  |  |     int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  |     if (fd < 0) { | 
					
						
							|  |  |  |         perror("open"); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-28 19:32:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Compute the final file size and ftruncate() to make writing fast.
 | 
					
						
							|  |  |  |     // FIXME: Remove this once the kernel is smart enough to do this instead.
 | 
					
						
							|  |  |  |     off_t file_size = 0; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t i = 0; i < line_count(); ++i) | 
					
						
							|  |  |  |         file_size += line(i).length(); | 
					
						
							|  |  |  |     file_size += line_count() - 1; | 
					
						
							| 
									
										
										
										
											2019-08-28 19:32:45 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     int rc = ftruncate(fd, file_size); | 
					
						
							|  |  |  |     if (rc < 0) { | 
					
						
							|  |  |  |         perror("ftruncate"); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t i = 0; i < line_count(); ++i) { | 
					
						
							|  |  |  |         auto& line = this->line(i); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  |         if (line.length()) { | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  |             auto line_as_utf8 = line.to_utf8(); | 
					
						
							|  |  |  |             ssize_t nwritten = write(fd, line_as_utf8.characters(), line_as_utf8.length()); | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  |             if (nwritten < 0) { | 
					
						
							|  |  |  |                 perror("write"); | 
					
						
							|  |  |  |                 close(fd); | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         if (i != line_count() - 1) { | 
					
						
							| 
									
										
										
										
											2019-03-07 17:06:11 +01:00
										 |  |  |             char ch = '\n'; | 
					
						
							|  |  |  |             ssize_t nwritten = write(fd, &ch, 1); | 
					
						
							|  |  |  |             if (nwritten != 1) { | 
					
						
							|  |  |  |                 perror("write"); | 
					
						
							|  |  |  |                 close(fd); | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     close(fd); | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | String TextEditor::text() const | 
					
						
							| 
									
										
										
										
											2019-03-15 17:37:13 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     StringBuilder builder; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t i = 0; i < line_count(); ++i) { | 
					
						
							|  |  |  |         auto& line = this->line(i); | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  |         builder.append(line.view()); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:37:13 +01:00
										 |  |  |         if (i != line_count() - 1) | 
					
						
							|  |  |  |             builder.append('\n'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return builder.to_string(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::clear() | 
					
						
							| 
									
										
										
										
											2019-03-15 17:37:13 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     document().remove_all_lines(); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     document().append_line(make<TextDocumentLine>(document())); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:37:13 +01:00
										 |  |  |     m_selection.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |     did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-03-15 17:37:13 +01:00
										 |  |  |     set_cursor(0, 0); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | String TextEditor::selected_text() const | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     if (!has_selection()) | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         return {}; | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-29 21:36:47 +01:00
										 |  |  |     return document().text_in_range(m_selection); | 
					
						
							| 
									
										
										
										
											2019-03-08 01:59:59 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::delete_selection() | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     auto selection = normalized_selection(); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |     execute<RemoveTextCommand>(selected_text(), selection); | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     m_selection.clear(); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |     did_update_selection(); | 
					
						
							| 
									
										
										
										
											2019-04-25 01:28:17 +02:00
										 |  |  |     did_change(); | 
					
						
							| 
									
										
										
										
											2019-03-08 18:28:24 +01:00
										 |  |  |     set_cursor(selection.start()); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::insert_at_cursor_or_replace_selection(const StringView& text) | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  |     ReflowDeferrer defer(*this); | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     ASSERT(is_editable()); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     if (has_selection()) | 
					
						
							|  |  |  |         delete_selection(); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |     execute<InsertTextCommand>(text, m_cursor); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::cut() | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     auto selected_text = this->selected_text(); | 
					
						
							|  |  |  |     printf("Cut: \"%s\"\n", selected_text.characters()); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     Clipboard::the().set_data(selected_text); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     delete_selection(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::copy() | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     auto selected_text = this->selected_text(); | 
					
						
							|  |  |  |     printf("Copy: \"%s\"\n", selected_text.characters()); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     Clipboard::the().set_data(selected_text); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::paste() | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     if (!is_editable()) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     auto paste_text = Clipboard::the().data(); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     printf("Paste: \"%s\"\n", paste_text.characters()); | 
					
						
							| 
									
										
										
										
											2020-01-23 21:07:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     TemporaryChange change(m_automatic_indentation_enabled, false); | 
					
						
							| 
									
										
										
										
											2019-03-08 14:08:15 +01:00
										 |  |  |     insert_at_cursor_or_replace_selection(paste_text); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-03-31 23:52:02 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  | void TextEditor::defer_reflow() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     ++m_reflow_deferred; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void TextEditor::undefer_reflow() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     ASSERT(m_reflow_deferred); | 
					
						
							|  |  |  |     if (!--m_reflow_deferred) { | 
					
						
							| 
									
										
										
										
											2020-05-29 15:43:06 -04:00
										 |  |  |         if (m_reflow_requested) { | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  |             recompute_all_visual_lines(); | 
					
						
							| 
									
										
										
										
											2020-05-29 15:43:06 -04:00
										 |  |  |             scroll_cursor_into_view(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::enter_event(Core::Event&) | 
					
						
							| 
									
										
										
										
											2019-03-31 23:52:02 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     ASSERT(window()); | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (!is_displayonly()) | 
					
						
							|  |  |  |         window()->set_override_cursor(StandardCursor::IBeam); | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     m_automatic_selection_scroll_timer->stop(); | 
					
						
							| 
									
										
										
										
											2019-03-31 23:52:02 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::leave_event(Core::Event&) | 
					
						
							| 
									
										
										
										
											2019-03-31 23:52:02 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     ASSERT(window()); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     window()->set_override_cursor(StandardCursor::None); | 
					
						
							| 
									
										
										
										
											2020-04-20 21:31:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (m_in_drag_select) | 
					
						
							|  |  |  |         m_automatic_selection_scroll_timer->start(); | 
					
						
							| 
									
										
										
										
											2019-03-31 23:52:02 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-04-09 16:20:36 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::did_change() | 
					
						
							| 
									
										
										
										
											2019-04-09 16:20:36 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     update_content_size(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							| 
									
										
										
										
											2019-11-09 01:50:39 -06:00
										 |  |  |     m_undo_action->set_enabled(can_undo()); | 
					
						
							|  |  |  |     m_redo_action->set_enabled(can_redo()); | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |     if (!m_has_pending_change_notification) { | 
					
						
							|  |  |  |         m_has_pending_change_notification = true; | 
					
						
							| 
									
										
										
										
											2019-06-07 11:46:02 +02:00
										 |  |  |         deferred_invoke([this](auto&) { | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |             if (!m_has_pending_change_notification) | 
					
						
							|  |  |  |                 return; | 
					
						
							| 
									
										
										
										
											2019-04-09 16:20:36 +02:00
										 |  |  |             if (on_change) | 
					
						
							|  |  |  |                 on_change(); | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  |             if (m_highlighter) | 
					
						
							| 
									
										
										
										
											2020-03-16 01:05:06 +02:00
										 |  |  |                 m_highlighter->rehighlight(palette()); | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |             m_has_pending_change_notification = false; | 
					
						
							| 
									
										
										
										
											2019-04-09 16:20:36 +02:00
										 |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  | void TextEditor::set_mode(const Mode mode) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_mode == mode) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_mode = mode; | 
					
						
							|  |  |  |     switch (mode) { | 
					
						
							|  |  |  |     case Editable: | 
					
						
							|  |  |  |         m_cut_action->set_enabled(true && has_selection()); | 
					
						
							|  |  |  |         m_delete_action->set_enabled(true); | 
					
						
							|  |  |  |         m_paste_action->set_enabled(true); | 
					
						
							|  |  |  |         set_accepts_emoji_input(true); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case DisplayOnly: | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     case ReadOnly: | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |         m_cut_action->set_enabled(false && has_selection()); | 
					
						
							|  |  |  |         m_delete_action->set_enabled(false); | 
					
						
							|  |  |  |         m_paste_action->set_enabled(false); | 
					
						
							|  |  |  |         set_accepts_emoji_input(false); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         ASSERT_NOT_REACHED(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  | void TextEditor::set_has_open_button(bool has_button) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (m_has_open_button == has_button) | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     m_has_open_button = has_button; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void TextEditor::set_has_visible_list(bool visible) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_has_visible_list == visible) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_has_visible_list = visible; | 
					
						
							| 
									
										
										
										
											2019-05-08 05:00:28 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::did_update_selection() | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-15 17:04:49 -04:00
										 |  |  |     m_cut_action->set_enabled(is_editable() && has_selection()); | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  |     m_copy_action->set_enabled(has_selection()); | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  |     if (on_selection_change) | 
					
						
							|  |  |  |         on_selection_change(); | 
					
						
							| 
									
										
										
										
											2019-08-25 10:23:11 +02:00
										 |  |  |     if (is_line_wrapping_enabled()) { | 
					
						
							|  |  |  |         // FIXME: Try to repaint less.
 | 
					
						
							|  |  |  |         update(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-04-12 02:52:34 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::context_menu_event(ContextMenuEvent& event) | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-14 17:02:46 -04:00
										 |  |  |     if (is_displayonly()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  |     if (!m_context_menu) { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |         m_context_menu = Menu::construct(); | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  |         m_context_menu->add_action(undo_action()); | 
					
						
							|  |  |  |         m_context_menu->add_action(redo_action()); | 
					
						
							|  |  |  |         m_context_menu->add_separator(); | 
					
						
							|  |  |  |         m_context_menu->add_action(cut_action()); | 
					
						
							|  |  |  |         m_context_menu->add_action(copy_action()); | 
					
						
							|  |  |  |         m_context_menu->add_action(paste_action()); | 
					
						
							|  |  |  |         m_context_menu->add_action(delete_action()); | 
					
						
							| 
									
										
										
										
											2020-04-20 21:10:03 +02:00
										 |  |  |         m_context_menu->add_separator(); | 
					
						
							|  |  |  |         m_context_menu->add_action(select_all_action()); | 
					
						
							| 
									
										
										
										
											2020-01-23 21:21:55 +01:00
										 |  |  |         if (is_multi_line()) { | 
					
						
							|  |  |  |             m_context_menu->add_separator(); | 
					
						
							| 
									
										
										
										
											2020-01-23 21:27:27 +01:00
										 |  |  |             m_context_menu->add_action(go_to_line_action()); | 
					
						
							| 
									
										
										
										
											2020-01-23 21:21:55 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-08-25 21:33:08 +02:00
										 |  |  |         if (!m_custom_context_menu_actions.is_empty()) { | 
					
						
							|  |  |  |             m_context_menu->add_separator(); | 
					
						
							|  |  |  |             for (auto& action : m_custom_context_menu_actions) { | 
					
						
							|  |  |  |                 m_context_menu->add_action(action); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-04-18 12:25:00 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     m_context_menu->popup(event.screen_position()); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-06 11:56:38 +01:00
										 |  |  | void TextEditor::set_text_alignment(Gfx::TextAlignment alignment) | 
					
						
							| 
									
										
										
										
											2019-04-24 22:24:16 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (m_text_alignment == alignment) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_text_alignment = alignment; | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-04-24 23:42:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::resize_event(ResizeEvent& event) | 
					
						
							| 
									
										
										
										
											2019-04-24 23:42:49 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     ScrollableWidget::resize_event(event); | 
					
						
							| 
									
										
										
										
											2019-04-24 23:42:49 +02:00
										 |  |  |     update_content_size(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							| 
									
										
										
										
											2019-04-24 23:42:49 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-16 13:36:21 +02:00
										 |  |  | void TextEditor::theme_change_event(ThemeChangeEvent& event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     ScrollableWidget::theme_change_event(event); | 
					
						
							|  |  |  |     if (m_highlighter) | 
					
						
							|  |  |  |         m_highlighter->rehighlight(palette()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_selection(const TextRange& selection) | 
					
						
							| 
									
										
										
										
											2019-08-21 21:23:17 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (m_selection == selection) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_selection = selection; | 
					
						
							|  |  |  |     set_cursor(m_selection.end()); | 
					
						
							|  |  |  |     scroll_position_into_view(normalized_selection().start()); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::clear_selection() | 
					
						
							| 
									
										
										
										
											2019-12-01 18:48:32 -08:00
										 |  |  | { | 
					
						
							|  |  |  |     if (!has_selection()) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_selection.clear(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::recompute_all_visual_lines() | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-05-27 19:18:40 +02:00
										 |  |  |     if (m_reflow_deferred) { | 
					
						
							|  |  |  |         m_reflow_requested = true; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 00:31:46 +02:00
										 |  |  |     m_reflow_requested = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     int y_offset = 0; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t line_index = 0; line_index < line_count(); ++line_index) { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |         recompute_visual_lines(line_index); | 
					
						
							|  |  |  |         m_line_visual_data[line_index].visual_rect.set_y(y_offset); | 
					
						
							|  |  |  |         y_offset += m_line_visual_data[line_index].visual_rect.height(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-08-28 23:26:24 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  |     update_content_size(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::ensure_cursor_is_valid() | 
					
						
							| 
									
										
										
										
											2019-11-05 16:37:47 -06:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |     auto new_cursor = m_cursor; | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     if (new_cursor.line() >= line_count()) | 
					
						
							|  |  |  |         new_cursor.set_line(line_count() - 1); | 
					
						
							|  |  |  |     if (new_cursor.column() > line(new_cursor.line()).length()) | 
					
						
							|  |  |  |         new_cursor.set_column(line(new_cursor.line()).length()); | 
					
						
							| 
									
										
										
										
											2019-11-30 16:50:24 +01:00
										 |  |  |     if (m_cursor != new_cursor) | 
					
						
							|  |  |  |         set_cursor(new_cursor); | 
					
						
							| 
									
										
										
										
											2019-11-05 16:37:47 -06:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | size_t TextEditor::visual_line_containing(size_t line_index, size_t column) const | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t visual_line_index = 0; | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |     for_each_visual_line(line_index, [&](const Gfx::IntRect&, auto& view, size_t start_of_visual_line) { | 
					
						
							| 
									
										
										
										
											2019-10-27 16:10:07 +01:00
										 |  |  |         if (column >= start_of_visual_line && ((column - start_of_visual_line) < view.length())) | 
					
						
							|  |  |  |             return IterationDecision::Break; | 
					
						
							|  |  |  |         ++visual_line_index; | 
					
						
							|  |  |  |         return IterationDecision::Continue; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     return visual_line_index; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::recompute_visual_lines(size_t line_index) | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     auto& line = document().line(line_index); | 
					
						
							|  |  |  |     auto& visual_data = m_line_visual_data[line_index]; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     visual_data.visual_line_breaks.clear_with_capacity(); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     int available_width = visible_text_rect_in_inner_coordinates().width(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (is_line_wrapping_enabled()) { | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         int line_width_so_far = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |         auto glyph_spacing = font().glyph_spacing(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |         for (size_t i = 0; i < line.length(); ++i) { | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  |             auto codepoint = line.codepoints()[i]; | 
					
						
							|  |  |  |             auto glyph_width = font().glyph_or_emoji_width(codepoint); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |             if ((line_width_so_far + glyph_width + glyph_spacing) > available_width) { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |                 visual_data.visual_line_breaks.append(i); | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |                 line_width_so_far = glyph_width + glyph_spacing; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |             line_width_so_far += glyph_width + glyph_spacing; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     visual_data.visual_line_breaks.append(line.length()); | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     if (is_line_wrapping_enabled()) | 
					
						
							| 
									
										
										
										
											2020-02-25 14:49:47 +01:00
										 |  |  |         visual_data.visual_rect = { m_horizontal_content_padding, 0, available_width, static_cast<int>(visual_data.visual_line_breaks.size()) * line_height() }; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |         visual_data.visual_rect = { m_horizontal_content_padding, 0, font().width(line.view()), line_height() }; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | template<typename Callback> | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::for_each_visual_line(size_t line_index, Callback callback) const | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |     auto editor_visible_text_rect = visible_text_rect_in_inner_coordinates(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     size_t start_of_line = 0; | 
					
						
							|  |  |  |     size_t visual_line_index = 0; | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     auto& line = document().line(line_index); | 
					
						
							|  |  |  |     auto& visual_data = m_line_visual_data[line_index]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (auto visual_line_break : visual_data.visual_line_breaks) { | 
					
						
							| 
									
										
										
										
											2020-05-17 20:33:06 +02:00
										 |  |  |         auto visual_line_view = Utf32View(line.codepoints() + start_of_line, visual_line_break - start_of_line); | 
					
						
							| 
									
										
										
										
											2020-06-10 10:57:59 +02:00
										 |  |  |         Gfx::IntRect visual_line_rect { | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |             visual_data.visual_rect.x(), | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |             visual_data.visual_rect.y() + ((int)visual_line_index * line_height()), | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |             font().width(visual_line_view), | 
					
						
							|  |  |  |             line_height() | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         }; | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |         if (is_right_text_alignment(text_alignment())) | 
					
						
							| 
									
										
										
										
											2019-09-16 20:57:32 +02:00
										 |  |  |             visual_line_rect.set_right_without_resize(editor_visible_text_rect.right()); | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  |         if (is_single_line()) { | 
					
						
							| 
									
										
										
										
											2019-09-16 20:57:32 +02:00
										 |  |  |             visual_line_rect.center_vertically_within(editor_visible_text_rect); | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  |             if (m_icon) | 
					
						
							|  |  |  |                 visual_line_rect.move_by(icon_size() + icon_padding(), 0); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |         if (callback(visual_line_rect, visual_line_view, start_of_line) == IterationDecision::Break) | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         start_of_line = visual_line_break; | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  |         ++visual_line_index; | 
					
						
							| 
									
										
										
										
											2019-08-25 08:43:01 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-08-25 12:23:14 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_line_wrapping_enabled(bool enabled) | 
					
						
							| 
									
										
										
										
											2019-08-25 12:23:14 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     if (m_line_wrapping_enabled == enabled) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_line_wrapping_enabled = enabled; | 
					
						
							| 
									
										
										
										
											2019-09-01 20:34:50 +02:00
										 |  |  |     horizontal_scrollbar().set_visible(!m_line_wrapping_enabled); | 
					
						
							| 
									
										
										
										
											2019-08-25 12:23:14 +02:00
										 |  |  |     update_content_size(); | 
					
						
							|  |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-08-25 14:04:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::add_custom_context_menu_action(Action& action) | 
					
						
							| 
									
										
										
										
											2019-08-25 21:33:08 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     m_custom_context_menu_actions.append(action); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-09-01 12:26:35 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::did_change_font() | 
					
						
							| 
									
										
										
										
											2019-09-01 12:26:35 +02:00
										 |  |  | { | 
					
						
							|  |  |  |     vertical_scrollbar().set_step(line_height()); | 
					
						
							| 
									
										
										
										
											2019-12-29 23:03:41 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  |     Widget::did_change_font(); | 
					
						
							| 
									
										
										
										
											2019-09-01 12:26:35 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_append_line() | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_line_visual_data.append(make<LineVisualData>()); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_remove_line(size_t line_index) | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_line_visual_data.remove(line_index); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_remove_all_lines() | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_line_visual_data.clear(); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_insert_line(size_t line_index) | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_line_visual_data.insert(line_index, make<LineVisualData>()); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_change() | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2019-11-30 18:54:53 +01:00
										 |  |  |     did_change(); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     update(); | 
					
						
							| 
									
										
										
										
											2019-10-27 18:00:07 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_set_text() | 
					
						
							| 
									
										
										
										
											2019-11-23 17:41:14 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     m_line_visual_data.clear(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t i = 0; i < m_document->line_count(); ++i) | 
					
						
							| 
									
										
										
										
											2019-11-23 17:41:14 +01:00
										 |  |  |         m_line_visual_data.append(make<LineVisualData>()); | 
					
						
							|  |  |  |     document_did_change(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::document_did_set_cursor(const TextPosition& position) | 
					
						
							| 
									
										
										
										
											2019-11-30 13:05:17 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     set_cursor(position); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::set_document(TextDocument& document) | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     if (m_document.ptr() == &document) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     if (m_document) | 
					
						
							|  |  |  |         m_document->unregister_client(*this); | 
					
						
							|  |  |  |     m_document = document; | 
					
						
							|  |  |  |     m_line_visual_data.clear(); | 
					
						
							| 
									
										
										
										
											2019-12-09 17:45:40 +01:00
										 |  |  |     for (size_t i = 0; i < m_document->line_count(); ++i) { | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |         m_line_visual_data.append(make<LineVisualData>()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     m_cursor = { 0, 0 }; | 
					
						
							| 
									
										
										
										
											2019-12-01 18:49:13 -08:00
										 |  |  |     if (has_selection()) | 
					
						
							|  |  |  |         m_selection.clear(); | 
					
						
							| 
									
										
										
										
											2019-10-27 19:36:59 +01:00
										 |  |  |     recompute_all_visual_lines(); | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  |     m_document->register_client(*this); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-11-02 14:22:49 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | void TextEditor::flush_pending_change_notification_if_needed() | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  | { | 
					
						
							|  |  |  |     if (!m_has_pending_change_notification) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     if (on_change) | 
					
						
							|  |  |  |         on_change(); | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  |     if (m_highlighter) | 
					
						
							| 
									
										
										
										
											2020-03-16 01:05:06 +02:00
										 |  |  |         m_highlighter->rehighlight(palette()); | 
					
						
							| 
									
										
										
										
											2019-11-08 19:49:08 +01:00
										 |  |  |     m_has_pending_change_notification = false; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-12 16:36:25 +02:00
										 |  |  | const SyntaxHighlighter* TextEditor::syntax_highlighter() const | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-03-12 17:23:54 +02:00
										 |  |  |     return m_highlighter.ptr(); | 
					
						
							| 
									
										
										
										
											2020-03-12 16:36:25 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  | void TextEditor::set_syntax_highlighter(OwnPtr<SyntaxHighlighter> highlighter) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_highlighter) | 
					
						
							|  |  |  |         m_highlighter->detach(); | 
					
						
							|  |  |  |     m_highlighter = move(highlighter); | 
					
						
							|  |  |  |     if (m_highlighter) { | 
					
						
							|  |  |  |         m_highlighter->attach(*this); | 
					
						
							| 
									
										
										
										
											2020-03-16 01:05:06 +02:00
										 |  |  |         m_highlighter->rehighlight(palette()); | 
					
						
							| 
									
										
										
										
											2020-03-11 03:01:52 +02:00
										 |  |  |     } else | 
					
						
							|  |  |  |         document().set_spans({}); | 
					
						
							| 
									
										
										
										
											2020-02-07 20:07:15 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-15 00:24:14 +01:00
										 |  |  | int TextEditor::line_height() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return font().glyph_height() + m_line_spacing; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  | int TextEditor::fixed_glyph_width() const | 
					
						
							| 
									
										
										
										
											2020-02-15 00:24:14 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2020-05-18 16:38:28 +02:00
										 |  |  |     ASSERT(font().is_fixed_width()); | 
					
						
							|  |  |  |     return font().glyph_width(' '); | 
					
						
							| 
									
										
										
										
											2020-02-15 00:24:14 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-29 20:34:42 +02:00
										 |  |  | void TextEditor::set_icon(const Gfx::Bitmap* icon) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_icon == icon) | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     m_icon = icon; | 
					
						
							|  |  |  |     update(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-02-02 15:07:41 +01:00
										 |  |  | } |