1/**************************************************************************/
2/* text_edit.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "text_edit.h"
32
33#include "core/config/project_settings.h"
34#include "core/input/input.h"
35#include "core/input/input_map.h"
36#include "core/object/message_queue.h"
37#include "core/object/script_language.h"
38#include "core/os/keyboard.h"
39#include "core/os/os.h"
40#include "core/string/string_builder.h"
41#include "core/string/translation.h"
42#include "scene/gui/label.h"
43#include "scene/main/window.h"
44#include "scene/theme/theme_db.h"
45
46///////////////////////////////////////////////////////////////////////////////
47/// TEXT ///
48///////////////////////////////////////////////////////////////////////////////
49
50void TextEdit::Text::set_font(const Ref<Font> &p_font) {
51 if (font == p_font) {
52 return;
53 }
54 font = p_font;
55 is_dirty = true;
56}
57
58void TextEdit::Text::set_font_size(int p_font_size) {
59 if (font_size == p_font_size) {
60 return;
61 }
62 font_size = p_font_size;
63 is_dirty = true;
64}
65
66void TextEdit::Text::set_tab_size(int p_tab_size) {
67 if (tab_size == p_tab_size) {
68 return;
69 }
70 tab_size = p_tab_size;
71 tab_size_dirty = true;
72}
73
74int TextEdit::Text::get_tab_size() const {
75 return tab_size;
76}
77
78void TextEdit::Text::set_direction_and_language(TextServer::Direction p_direction, const String &p_language) {
79 if (direction == p_direction && language == p_language) {
80 return;
81 }
82 direction = p_direction;
83 language = p_language;
84 is_dirty = true;
85}
86
87void TextEdit::Text::set_draw_control_chars(bool p_enabled) {
88 if (draw_control_chars == p_enabled) {
89 return;
90 }
91 draw_control_chars = p_enabled;
92 is_dirty = true;
93}
94
95int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
96 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
97 if (p_wrap_index != -1) {
98 return text[p_line].data_buf->get_line_width(p_wrap_index);
99 }
100 return text[p_line].data_buf->get_size().x;
101}
102
103int TextEdit::Text::get_line_height() const {
104 return line_height;
105}
106
107void TextEdit::Text::set_width(float p_width) {
108 width = p_width;
109}
110
111float TextEdit::Text::get_width() const {
112 return width;
113}
114
115void TextEdit::Text::set_brk_flags(BitField<TextServer::LineBreakFlag> p_flags) {
116 brk_flags = p_flags;
117}
118
119BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const {
120 return brk_flags;
121}
122
123int TextEdit::Text::get_line_wrap_amount(int p_line) const {
124 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
125
126 return text[p_line].data_buf->get_line_count() - 1;
127}
128
129Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const {
130 Vector<Vector2i> ret;
131 ERR_FAIL_INDEX_V(p_line, text.size(), ret);
132
133 for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) {
134 ret.push_back(text[p_line].data_buf->get_line_range(i));
135 }
136 return ret;
137}
138
139const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const {
140 ERR_FAIL_INDEX_V(p_line, text.size(), Ref<TextParagraph>());
141 return text[p_line].data_buf;
142}
143
144_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const {
145 return text[p_line].data;
146}
147
148void TextEdit::Text::_calculate_line_height() {
149 int height = 0;
150 for (const Line &l : text) {
151 // Found another line with the same height...nothing to update.
152 if (l.height == line_height) {
153 height = line_height;
154 break;
155 }
156 height = MAX(height, l.height);
157 }
158 line_height = height;
159}
160
161void TextEdit::Text::_calculate_max_line_width() {
162 int line_width = 0;
163 for (const Line &l : text) {
164 if (l.hidden) {
165 continue;
166 }
167
168 // Found another line with the same width...nothing to update.
169 if (l.width == max_width) {
170 line_width = max_width;
171 break;
172 }
173 line_width = MAX(line_width, l.width);
174 }
175 max_width = line_width;
176}
177
178void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) {
179 ERR_FAIL_INDEX(p_line, text.size());
180
181 if (font.is_null()) {
182 return; // Not in tree?
183 }
184
185 if (p_text_changed) {
186 text.write[p_line].data_buf->clear();
187 }
188
189 text.write[p_line].data_buf->set_width(width);
190 text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
191 text.write[p_line].data_buf->set_break_flags(brk_flags);
192 text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
193 if (p_ime_text.length() > 0) {
194 if (p_text_changed) {
195 text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language);
196 }
197 if (!p_bidi_override.is_empty()) {
198 TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override);
199 }
200 } else {
201 if (p_text_changed) {
202 text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language);
203 }
204 if (!text[p_line].bidi_override.is_empty()) {
205 TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override);
206 }
207 }
208
209 if (!p_text_changed) {
210 RID r = text.write[p_line].data_buf->get_rid();
211 int spans = TS->shaped_get_span_count(r);
212 for (int i = 0; i < spans; i++) {
213 TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features());
214 }
215 for (int i = 0; i < TextServer::SPACING_MAX; i++) {
216 TS->shaped_text_set_spacing(r, TextServer::SpacingType(i), font->get_spacing(TextServer::SpacingType(i)));
217 }
218 }
219
220 // Apply tab align.
221 if (tab_size > 0) {
222 Vector<float> tabs;
223 tabs.push_back(font->get_char_size(' ', font_size).width * tab_size);
224 text.write[p_line].data_buf->tab_align(tabs);
225 }
226
227 // Update height.
228 const int old_height = text.write[p_line].height;
229 const int wrap_amount = get_line_wrap_amount(p_line);
230 int height = font_height;
231 for (int i = 0; i <= wrap_amount; i++) {
232 height = MAX(height, text[p_line].data_buf->get_line_size(i).y);
233 }
234 text.write[p_line].height = height;
235
236 // If this line has shrunk, this may no longer the the tallest line.
237 if (old_height == line_height && height < line_height) {
238 _calculate_line_height();
239 } else {
240 line_height = MAX(height, line_height);
241 }
242
243 // Update width.
244 const int old_width = text.write[p_line].width;
245 int line_width = get_line_width(p_line);
246 text.write[p_line].width = line_width;
247
248 // If this line has shrunk, this may no longer the the longest line.
249 if (old_width == max_width && line_width < max_width) {
250 _calculate_max_line_width();
251 } else if (!is_hidden(p_line)) {
252 max_width = MAX(line_width, max_width);
253 }
254}
255
256void TextEdit::Text::invalidate_all_lines() {
257 for (int i = 0; i < text.size(); i++) {
258 text.write[i].data_buf->set_width(width);
259 text.write[i].data_buf->set_break_flags(brk_flags);
260 if (tab_size_dirty) {
261 if (tab_size > 0) {
262 Vector<float> tabs;
263 tabs.push_back(font->get_char_size(' ', font_size).width * tab_size);
264 text.write[i].data_buf->tab_align(tabs);
265 }
266 }
267 text.write[i].width = get_line_width(i);
268 }
269 tab_size_dirty = false;
270
271 _calculate_max_line_width();
272}
273
274void TextEdit::Text::invalidate_font() {
275 if (!is_dirty) {
276 return;
277 }
278
279 max_width = -1;
280 line_height = -1;
281
282 if (font.is_valid() && font_size > 0) {
283 font_height = font->get_height(font_size);
284 }
285
286 for (int i = 0; i < text.size(); i++) {
287 invalidate_cache(i, -1, false);
288 }
289 is_dirty = false;
290}
291
292void TextEdit::Text::invalidate_all() {
293 if (!is_dirty) {
294 return;
295 }
296
297 max_width = -1;
298 line_height = -1;
299
300 if (font.is_valid() && font_size > 0) {
301 font_height = font->get_height(font_size);
302 }
303
304 for (int i = 0; i < text.size(); i++) {
305 invalidate_cache(i, -1, true);
306 }
307 is_dirty = false;
308}
309
310void TextEdit::Text::clear() {
311 text.clear();
312
313 max_width = -1;
314 line_height = -1;
315
316 Line line;
317 line.gutters.resize(gutter_count);
318 line.data = "";
319 text.insert(0, line);
320 invalidate_cache(0, -1, true);
321}
322
323int TextEdit::Text::get_max_width() const {
324 return max_width;
325}
326
327void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) {
328 ERR_FAIL_INDEX(p_line, text.size());
329
330 text.write[p_line].data = p_text;
331 text.write[p_line].bidi_override = p_bidi_override;
332 invalidate_cache(p_line, -1, true);
333}
334
335void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) {
336 int new_line_count = p_text.size() - 1;
337 if (new_line_count > 0) {
338 text.resize(text.size() + new_line_count);
339 for (int i = (text.size() - 1); i > p_at; i--) {
340 if ((i - new_line_count) <= 0) {
341 break;
342 }
343 text.write[i] = text[i - new_line_count];
344 }
345 }
346
347 for (int i = 0; i < p_text.size(); i++) {
348 if (i == 0) {
349 set(p_at + i, p_text[i], p_bidi_override[i]);
350 continue;
351 }
352 Line line;
353 line.gutters.resize(gutter_count);
354 line.data = p_text[i];
355 line.bidi_override = p_bidi_override[i];
356 text.write[p_at + i] = line;
357 invalidate_cache(p_at + i, -1, true);
358 }
359}
360
361void TextEdit::Text::remove_range(int p_from_line, int p_to_line) {
362 if (p_from_line == p_to_line) {
363 return;
364 }
365
366 bool dirty_height = false;
367 bool dirty_width = false;
368 for (int i = p_from_line; i < p_to_line; i++) {
369 if (!dirty_height && text[i].height == line_height) {
370 dirty_height = true;
371 }
372
373 if (!dirty_width && text[i].width == max_width) {
374 dirty_width = true;
375 }
376
377 if (dirty_height && dirty_width) {
378 break;
379 }
380 }
381
382 int diff = (p_to_line - p_from_line);
383 for (int i = p_to_line; i < text.size() - 1; i++) {
384 text.write[(i - diff) + 1] = text[i + 1];
385 }
386 text.resize(text.size() - diff);
387
388 if (dirty_height) {
389 _calculate_line_height();
390 }
391
392 if (dirty_width) {
393 _calculate_max_line_width();
394 }
395}
396
397void TextEdit::Text::add_gutter(int p_at) {
398 for (int i = 0; i < text.size(); i++) {
399 if (p_at < 0 || p_at > gutter_count) {
400 text.write[i].gutters.push_back(Gutter());
401 } else {
402 text.write[i].gutters.insert(p_at, Gutter());
403 }
404 }
405 gutter_count++;
406}
407
408void TextEdit::Text::remove_gutter(int p_gutter) {
409 for (int i = 0; i < text.size(); i++) {
410 text.write[i].gutters.remove_at(p_gutter);
411 }
412 gutter_count--;
413}
414
415void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) {
416 text.write[p_to_line].gutters = text[p_from_line].gutters;
417 text.write[p_from_line].gutters.clear();
418 text.write[p_from_line].gutters.resize(gutter_count);
419}
420
421///////////////////////////////////////////////////////////////////////////////
422/// TEXT EDIT ///
423///////////////////////////////////////////////////////////////////////////////
424
425void TextEdit::_notification(int p_what) {
426 switch (p_what) {
427 case NOTIFICATION_POSTINITIALIZE: {
428 _update_caches();
429 } break;
430
431 case NOTIFICATION_ENTER_TREE: {
432 _update_caches();
433 if (caret_pos_dirty) {
434 MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
435 }
436 if (text_changed_dirty) {
437 MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
438 }
439 _update_wrap_at_column(true);
440 } break;
441
442 case NOTIFICATION_RESIZED: {
443 _update_scrollbars();
444 _update_wrap_at_column();
445 } break;
446
447 case NOTIFICATION_VISIBILITY_CHANGED: {
448 if (is_visible()) {
449 call_deferred(SNAME("_update_scrollbars"));
450 call_deferred(SNAME("_update_wrap_at_column"));
451 }
452 } break;
453
454 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
455 case NOTIFICATION_TRANSLATION_CHANGED:
456 case NOTIFICATION_THEME_CHANGED: {
457 if (is_inside_tree()) {
458 _update_caches();
459 _update_wrap_at_column(true);
460 }
461 } break;
462
463 case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
464 window_has_focus = true;
465 draw_caret = true;
466 queue_redraw();
467 } break;
468
469 case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
470 window_has_focus = false;
471 draw_caret = false;
472 queue_redraw();
473 } break;
474
475 case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
476 if (scrolling && get_v_scroll() != target_v_scroll) {
477 double target_y = target_v_scroll - get_v_scroll();
478 double dist = abs(target_y);
479 // To ensure minimap is responsive override the speed setting.
480 double vel = ((target_y / dist) * ((minimap_clicked) ? 3000 : v_scroll_speed)) * get_physics_process_delta_time();
481
482 // Prevent small velocities from blocking scrolling.
483 if (Math::abs(vel) < v_scroll->get_step()) {
484 vel = v_scroll->get_step() * SIGN(vel);
485 }
486
487 if (Math::abs(vel) >= dist) {
488 set_v_scroll(target_v_scroll);
489 scrolling = false;
490 minimap_clicked = false;
491 set_physics_process_internal(false);
492 } else {
493 set_v_scroll(get_v_scroll() + vel);
494 }
495 } else {
496 scrolling = false;
497 minimap_clicked = false;
498 set_physics_process_internal(false);
499 }
500 } break;
501
502 case NOTIFICATION_DRAW: {
503 if (first_draw) {
504 // Size may not be the final one, so attempts to ensure caret was visible may have failed.
505 adjust_viewport_to_caret();
506 first_draw = false;
507 }
508
509 /* Prevent the resource getting lost between the editor and game. */
510 if (Engine::get_singleton()->is_editor_hint()) {
511 if (syntax_highlighter.is_valid() && syntax_highlighter->get_text_edit() != this) {
512 syntax_highlighter->set_text_edit(this);
513 }
514 }
515
516 Size2 size = get_size();
517 bool rtl = is_layout_rtl();
518 if ((!has_focus() && !(menu && menu->has_focus())) || !window_has_focus) {
519 draw_caret = false;
520 }
521
522 _update_scrollbars();
523
524 RID ci = get_canvas_item();
525 RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
526 int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding;
527
528 int xmargin_end = size.width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
529 if (draw_minimap) {
530 xmargin_end -= minimap_width;
531 }
532 // Let's do it easy for now.
533 theme_cache.style_normal->draw(ci, Rect2(Point2(), size));
534 if (!editable) {
535 theme_cache.style_readonly->draw(ci, Rect2(Point2(), size));
536 draw_caret = is_drawing_caret_when_editable_disabled();
537 }
538 if (has_focus()) {
539 theme_cache.style_focus->draw(ci, Rect2(Point2(), size));
540 }
541
542 int visible_rows = get_visible_line_count() + 1;
543
544 Color color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
545
546 if (theme_cache.background_color.a > 0.01) {
547 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), theme_cache.background_color);
548 }
549
550 Vector<BraceMatchingData> brace_matching;
551 if (highlight_matching_braces_enabled) {
552 brace_matching.resize(carets.size());
553
554 for (int caret = 0; caret < carets.size(); caret++) {
555 if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) {
556 continue;
557 }
558
559 if (get_caret_column(caret) < text[get_caret_line(caret)].length()) {
560 // Check for open.
561 char32_t c = text[get_caret_line(caret)][get_caret_column(caret)];
562 char32_t closec = 0;
563
564 if (c == '[') {
565 closec = ']';
566 } else if (c == '{') {
567 closec = '}';
568 } else if (c == '(') {
569 closec = ')';
570 }
571
572 if (closec != 0) {
573 int stack = 1;
574
575 for (int i = get_caret_line(caret); i < text.size(); i++) {
576 int from = i == get_caret_line(caret) ? get_caret_column(caret) + 1 : 0;
577 for (int j = from; j < text[i].length(); j++) {
578 char32_t cc = text[i][j];
579 // Ignore any brackets inside a string.
580 if (cc == '"' || cc == '\'') {
581 char32_t quotation = cc;
582 do {
583 j++;
584 if (!(j < text[i].length())) {
585 break;
586 }
587 cc = text[i][j];
588 // Skip over escaped quotation marks inside strings.
589 if (cc == '\\') {
590 bool escaped = true;
591 while (j + 1 < text[i].length() && text[i][j + 1] == '\\') {
592 escaped = !escaped;
593 j++;
594 }
595 if (escaped) {
596 j++;
597 continue;
598 }
599 }
600 } while (cc != quotation);
601 } else if (cc == c) {
602 stack++;
603 } else if (cc == closec) {
604 stack--;
605 }
606
607 if (stack == 0) {
608 brace_matching.write[caret].open_match_line = i;
609 brace_matching.write[caret].open_match_column = j;
610 brace_matching.write[caret].open_matching = true;
611
612 break;
613 }
614 }
615 if (brace_matching.write[caret].open_match_line != -1) {
616 break;
617 }
618 }
619
620 if (!brace_matching.write[caret].open_matching) {
621 brace_matching.write[caret].open_mismatch = true;
622 }
623 }
624 }
625
626 if (get_caret_column(caret) > 0) {
627 char32_t c = text[get_caret_line(caret)][get_caret_column(caret) - 1];
628 char32_t closec = 0;
629
630 if (c == ']') {
631 closec = '[';
632 } else if (c == '}') {
633 closec = '{';
634 } else if (c == ')') {
635 closec = '(';
636 }
637
638 if (closec != 0) {
639 int stack = 1;
640
641 for (int i = get_caret_line(caret); i >= 0; i--) {
642 int from = i == get_caret_line(caret) ? get_caret_column(caret) - 2 : text[i].length() - 1;
643 for (int j = from; j >= 0; j--) {
644 char32_t cc = text[i][j];
645 // Ignore any brackets inside a string.
646 if (cc == '"' || cc == '\'') {
647 char32_t quotation = cc;
648 do {
649 j--;
650 if (!(j >= 0)) {
651 break;
652 }
653 cc = text[i][j];
654 // Skip over escaped quotation marks inside strings.
655 if (cc == quotation) {
656 bool escaped = false;
657 while (j - 1 >= 0 && text[i][j - 1] == '\\') {
658 escaped = !escaped;
659 j--;
660 }
661 if (escaped) {
662 cc = '\\';
663 continue;
664 }
665 }
666 } while (cc != quotation);
667 } else if (cc == c) {
668 stack++;
669 } else if (cc == closec) {
670 stack--;
671 }
672
673 if (stack == 0) {
674 brace_matching.write[caret].close_match_line = i;
675 brace_matching.write[caret].close_match_column = j;
676 brace_matching.write[caret].close_matching = true;
677
678 break;
679 }
680 }
681 if (brace_matching.write[caret].close_match_line != -1) {
682 break;
683 }
684 }
685
686 if (!brace_matching.write[caret].close_matching) {
687 brace_matching.write[caret].close_mismatch = true;
688 }
689 }
690 }
691 }
692 }
693
694 bool draw_placeholder = text.size() == 1 && text[0].is_empty() && ime_text.is_empty();
695
696 // Get the highlighted words.
697 String highlighted_text = get_selected_text(0);
698
699 // Check if highlighted words contain only whitespaces (tabs or spaces).
700 bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty();
701
702 HashMap<int, HashSet<int>> caret_line_wrap_index_map;
703 Vector<int> carets_wrap_index;
704 carets_wrap_index.resize(carets.size());
705 for (int i = 0; i < carets.size(); i++) {
706 carets.write[i].visible = false;
707 int wrap_index = get_caret_wrap_index(i);
708 caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index);
709 carets_wrap_index.write[i] = wrap_index;
710 }
711
712 int first_vis_line = get_first_visible_line() - 1;
713 int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
714 draw_amount += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(first_vis_line + 1);
715
716 // Draw minimap.
717 if (draw_minimap) {
718 int minimap_visible_lines = get_minimap_visible_lines();
719 int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
720 int minimap_tab_size = minimap_char_size.x * text.get_tab_size();
721
722 // Calculate viewport size and y offset.
723 int viewport_height = (draw_amount - 1) * minimap_line_height;
724 int control_height = _get_control_height() - viewport_height;
725 int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount));
726
727 // Calculate the first line.
728 int num_lines_before = round((viewport_offset_y) / minimap_line_height);
729 int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line;
730 if (minimap_line >= 0) {
731 minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x;
732 minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0);
733 }
734 int minimap_draw_amount = minimap_visible_lines + get_line_wrap_count(minimap_line + 1);
735
736 // Draw the minimap.
737
738 // Add visual feedback when dragging or hovering the the visible area rectangle.
739 float viewport_alpha;
740 if (dragging_minimap) {
741 viewport_alpha = 0.25;
742 } else if (hovering_minimap) {
743 viewport_alpha = 0.175;
744 } else {
745 viewport_alpha = 0.1;
746 }
747
748 const Color viewport_color = (theme_cache.background_color.get_v() < 0.5) ? Color(1, 1, 1, viewport_alpha) : Color(0, 0, 0, viewport_alpha);
749 if (rtl) {
750 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, viewport_offset_y, minimap_width, viewport_height), viewport_color);
751 } else {
752 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), viewport_offset_y, minimap_width, viewport_height), viewport_color);
753 }
754
755 for (int i = 0; i < minimap_draw_amount; i++) {
756 minimap_line++;
757
758 if (minimap_line < 0 || minimap_line >= (int)text.size()) {
759 break;
760 }
761
762 while (_is_line_hidden(minimap_line)) {
763 minimap_line++;
764 if (minimap_line < 0 || minimap_line >= (int)text.size()) {
765 break;
766 }
767 }
768
769 if (minimap_line < 0 || minimap_line >= (int)text.size()) {
770 break;
771 }
772
773 Dictionary color_map = _get_line_syntax_highlighting(minimap_line);
774
775 Color line_background_color = text.get_line_background_color(minimap_line);
776 line_background_color.a *= 0.6;
777 Color current_color = theme_cache.font_color;
778 if (!editable) {
779 current_color = theme_cache.font_readonly_color;
780 }
781
782 Vector<String> wrap_rows = get_line_wrapped_text(minimap_line);
783 int line_wrap_amount = get_line_wrap_count(minimap_line);
784 int last_wrap_column = 0;
785
786 for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) {
787 if (line_wrap_index != 0) {
788 i++;
789 if (i >= minimap_draw_amount) {
790 break;
791 }
792 }
793
794 const String &str = wrap_rows[line_wrap_index];
795 int indent_px = line_wrap_index != 0 ? get_indent_level(minimap_line) : 0;
796 if (indent_px >= wrap_at_column) {
797 indent_px = 0;
798 }
799 indent_px = minimap_char_size.x * indent_px;
800
801 if (line_wrap_index > 0) {
802 last_wrap_column += wrap_rows[line_wrap_index - 1].length();
803 }
804
805 if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) {
806 if (rtl) {
807 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color);
808 } else {
809 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color);
810 }
811 } else if (line_background_color != Color(0, 0, 0, 0)) {
812 if (rtl) {
813 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color);
814 } else {
815 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), line_background_color);
816 }
817 }
818
819 Color previous_color;
820 int characters = 0;
821 int tabs = 0;
822 for (int j = 0; j < str.length(); j++) {
823 const Variant *color_data = color_map.getptr(last_wrap_column + j);
824 if (color_data != nullptr) {
825 current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
826 if (!editable) {
827 current_color.a = theme_cache.font_readonly_color.a;
828 }
829 }
830 color = current_color;
831
832 if (j == 0) {
833 previous_color = color;
834 }
835
836 int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs;
837 bool out_of_bounds = (xpos >= xmargin_end + minimap_width);
838
839 bool whitespace = is_whitespace(str[j]);
840 if (!whitespace) {
841 characters++;
842
843 if (j < str.length() - 1 && color == previous_color && !out_of_bounds) {
844 continue;
845 }
846
847 // If we've changed color we are at the start of a new section, therefore we need to go back to the end
848 // of the previous section to draw it, we'll also add the character back on.
849 if (color != previous_color) {
850 characters--;
851 j--;
852
853 if (str[j] == '\t') {
854 tabs -= minimap_tab_size;
855 }
856 }
857 }
858
859 if (characters > 0) {
860 previous_color.a *= 0.6;
861 // Take one for zero indexing, and if we hit whitespace / the end of a word.
862 int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1;
863 int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs;
864 if (rtl) {
865 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
866 } else {
867 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(char_x_ofs, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
868 }
869 }
870
871 if (out_of_bounds) {
872 break;
873 }
874
875 if (str[j] == '\t') {
876 tabs += minimap_tab_size;
877 }
878
879 previous_color = color;
880 characters = 0;
881 }
882 }
883 }
884 }
885
886 int top_limit_y = 0;
887 int bottom_limit_y = get_size().height;
888 if (!editable) {
889 top_limit_y += theme_cache.style_readonly->get_margin(SIDE_TOP);
890 bottom_limit_y -= theme_cache.style_readonly->get_margin(SIDE_BOTTOM);
891 } else {
892 top_limit_y += theme_cache.style_normal->get_margin(SIDE_TOP);
893 bottom_limit_y -= theme_cache.style_normal->get_margin(SIDE_BOTTOM);
894 }
895
896 // Draw main text.
897 line_drawing_cache.clear();
898 int row_height = draw_placeholder ? placeholder_line_height + theme_cache.line_spacing : get_line_height();
899 int line = first_vis_line;
900 for (int i = 0; i < draw_amount; i++) {
901 line++;
902
903 if (line < 0 || line >= (int)text.size()) {
904 continue;
905 }
906
907 while (_is_line_hidden(line)) {
908 line++;
909 if (line < 0 || line >= (int)text.size()) {
910 break;
911 }
912 }
913
914 if (line < 0 || line >= (int)text.size()) {
915 continue;
916 }
917
918 LineDrawingCache cache_entry;
919
920 Dictionary color_map = _get_line_syntax_highlighting(line);
921
922 // Ensure we at least use the font color.
923 Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color;
924 if (draw_placeholder) {
925 current_color = theme_cache.font_placeholder_color;
926 }
927
928 const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line);
929
930 Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line);
931 int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line);
932
933 for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) {
934 if (line_wrap_index != 0) {
935 i++;
936 if (i >= draw_amount) {
937 break;
938 }
939 }
940
941 const String &str = wrap_rows[line_wrap_index];
942 int char_margin = xmargin_beg - first_visible_col;
943
944 int ofs_x = 0;
945 int ofs_y = 0;
946 if (!editable) {
947 ofs_x = theme_cache.style_readonly->get_offset().x / 2;
948 ofs_x -= theme_cache.style_normal->get_offset().x / 2;
949 ofs_y = theme_cache.style_readonly->get_offset().y / 2;
950 } else {
951 ofs_y = theme_cache.style_normal->get_offset().y / 2;
952 }
953
954 ofs_y += i * row_height + theme_cache.line_spacing / 2;
955 ofs_y -= first_visible_line_wrap_ofs * row_height;
956 ofs_y -= _get_v_scroll_offset() * row_height;
957
958 bool clipped = false;
959 if (ofs_y + row_height < top_limit_y) {
960 // Line is outside the top margin, clip current line.
961 // Still need to go through the process to prepare color changes for next lines.
962 clipped = true;
963 }
964
965 if (ofs_y > bottom_limit_y) {
966 // Line is outside the bottom margin, clip any remaining text.
967 i = draw_amount;
968 break;
969 }
970
971 if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) {
972 if (rtl) {
973 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
974 } else {
975 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line));
976 }
977 }
978
979 if (str.length() == 0) {
980 // Draw line background if empty as we won't loop at all.
981 if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
982 if (rtl) {
983 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
984 } else {
985 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
986 }
987 }
988
989 // Give visual indication of empty selected line.
990 for (int c = 0; c < carets.size(); c++) {
991 if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) {
992 float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
993 if (rtl) {
994 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), theme_cache.selection_color);
995 } else {
996 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), theme_cache.selection_color);
997 }
998 }
999 }
1000 } else {
1001 // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later.
1002 if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) {
1003 if (rtl) {
1004 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
1005 } else {
1006 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color);
1007 }
1008 }
1009 }
1010
1011 if (line_wrap_index == 0) {
1012 // Only do these if we are on the first wrapped part of a line.
1013
1014 cache_entry.y_offset = ofs_y;
1015
1016 int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT);
1017 for (int g = 0; g < gutters.size(); g++) {
1018 const GutterInfo gutter = gutters[g];
1019
1020 if (!gutter.draw || gutter.width <= 0) {
1021 continue;
1022 }
1023
1024 switch (gutter.type) {
1025 case GUTTER_TYPE_STRING: {
1026 const String &txt = get_line_gutter_text(line, g);
1027 if (txt.is_empty()) {
1028 break;
1029 }
1030
1031 Ref<TextLine> tl;
1032 tl.instantiate();
1033 tl->add_string(txt, theme_cache.font, theme_cache.font_size);
1034
1035 int yofs = ofs_y + (row_height - tl->get_size().y) / 2;
1036 if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
1037 tl->draw_outline(ci, Point2(gutter_offset + ofs_x, yofs), theme_cache.outline_size, theme_cache.outline_color);
1038 }
1039 tl->draw(ci, Point2(gutter_offset + ofs_x, yofs), get_line_gutter_item_color(line, g));
1040 } break;
1041 case GUTTER_TYPE_ICON: {
1042 const Ref<Texture2D> icon = get_line_gutter_icon(line, g);
1043 if (icon.is_null()) {
1044 break;
1045 }
1046
1047 Rect2 gutter_rect = Rect2(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height));
1048
1049 int horizontal_padding = gutter_rect.size.x / 6;
1050 int vertical_padding = gutter_rect.size.y / 6;
1051
1052 gutter_rect.position += Point2(horizontal_padding, vertical_padding);
1053 gutter_rect.size -= Point2(horizontal_padding, vertical_padding) * 2;
1054
1055 // Correct icon aspect ratio.
1056 float icon_ratio = icon->get_width() / icon->get_height();
1057 float gutter_ratio = gutter_rect.size.x / gutter_rect.size.y;
1058 if (gutter_ratio > icon_ratio) {
1059 gutter_rect.size.x = floor(icon->get_width() * (gutter_rect.size.y / icon->get_height()));
1060 } else {
1061 gutter_rect.size.y = floor(icon->get_height() * (gutter_rect.size.x / icon->get_width()));
1062 }
1063 if (rtl) {
1064 gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
1065 }
1066
1067 icon->draw_rect(ci, gutter_rect, false, get_line_gutter_item_color(line, g));
1068 } break;
1069 case GUTTER_TYPE_CUSTOM: {
1070 if (gutter.custom_draw_callback.is_valid()) {
1071 Rect2i gutter_rect = Rect2i(Point2i(gutter_offset, ofs_y), Size2i(gutter.width, row_height));
1072 if (rtl) {
1073 gutter_rect.position.x = size.width - gutter_rect.position.x - gutter_rect.size.x;
1074 }
1075
1076 Variant args[3] = { line, g, Rect2(gutter_rect) };
1077 const Variant *argp[] = { &args[0], &args[1], &args[2] };
1078 Callable::CallError ce;
1079 Variant ret;
1080 gutter.custom_draw_callback.callp(argp, 3, ret, ce);
1081 }
1082 } break;
1083 }
1084
1085 gutter_offset += gutter.width;
1086 }
1087 }
1088
1089 // Draw line.
1090 RID rid = ldata->get_line_rid(line_wrap_index);
1091 float text_height = TS->shaped_text_get_size(rid).y;
1092
1093 if (rtl) {
1094 char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x;
1095 }
1096
1097 for (int c = 0; c < carets.size(); c++) {
1098 if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection
1099 int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
1100 int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
1101 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to);
1102 for (int j = 0; j < sel.size(); j++) {
1103 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
1104 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1105 continue;
1106 }
1107 if (rect.position.x < xmargin_beg) {
1108 rect.size.x -= (xmargin_beg - rect.position.x);
1109 rect.position.x = xmargin_beg;
1110 }
1111 if (rect.position.x + rect.size.x > xmargin_end) {
1112 rect.size.x = xmargin_end - rect.position.x;
1113 }
1114 draw_rect(rect, theme_cache.selection_color, true);
1115 }
1116 }
1117 }
1118
1119 int start = TS->shaped_text_get_range(rid).x;
1120 if (!clipped && !search_text.is_empty()) { // Search highlight
1121 int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
1122 int search_text_len = search_text.length();
1123 while (search_text_col != -1) {
1124 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start);
1125 for (int j = 0; j < sel.size(); j++) {
1126 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
1127 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1128 continue;
1129 }
1130 if (rect.position.x < xmargin_beg) {
1131 rect.size.x -= (xmargin_beg - rect.position.x);
1132 rect.position.x = xmargin_beg;
1133 } else if (rect.position.x + rect.size.x > xmargin_end) {
1134 rect.size.x = xmargin_end - rect.position.x;
1135 }
1136 draw_rect(rect, theme_cache.search_result_color, true);
1137 draw_rect(rect, theme_cache.search_result_border_color, false);
1138 }
1139
1140 search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + search_text_len);
1141 }
1142 }
1143
1144 if (!clipped && highlight_all_occurrences && !only_whitespaces_highlighted && !highlighted_text.is_empty()) { // Highlight
1145 int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
1146 int highlighted_text_len = highlighted_text.length();
1147 while (highlighted_text_col != -1) {
1148 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start);
1149 for (int j = 0; j < sel.size(); j++) {
1150 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height);
1151 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1152 continue;
1153 }
1154 if (rect.position.x < xmargin_beg) {
1155 rect.size.x -= (xmargin_beg - rect.position.x);
1156 rect.position.x = xmargin_beg;
1157 } else if (rect.position.x + rect.size.x > xmargin_end) {
1158 rect.size.x = xmargin_end - rect.position.x;
1159 }
1160 draw_rect(rect, theme_cache.word_highlighted_color);
1161 }
1162
1163 highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len);
1164 }
1165 }
1166
1167 if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word
1168 if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
1169 int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
1170 int lookup_symbol_word_len = lookup_symbol_word.length();
1171 while (lookup_symbol_word_col != -1) {
1172 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start);
1173 for (int j = 0; j < sel.size(); j++) {
1174 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height);
1175 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1176 continue;
1177 }
1178 if (rect.position.x < xmargin_beg) {
1179 rect.size.x -= (xmargin_beg - rect.position.x);
1180 rect.position.x = xmargin_beg;
1181 } else if (rect.position.x + rect.size.x > xmargin_end) {
1182 rect.size.x = xmargin_end - rect.position.x;
1183 }
1184 rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size));
1185 rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size));
1186 draw_rect(rect, color);
1187 }
1188
1189 lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len);
1190 }
1191 }
1192 }
1193
1194 ofs_y += (row_height - text_height) / 2;
1195
1196 const Glyph *glyphs = TS->shaped_text_get_glyphs(rid);
1197 int gl_size = TS->shaped_text_get_glyph_count(rid);
1198
1199 ofs_y += ldata->get_line_ascent(line_wrap_index);
1200
1201 int first_visible_char = TS->shaped_text_get_range(rid).y;
1202 int last_visible_char = TS->shaped_text_get_range(rid).x;
1203
1204 float char_ofs = 0;
1205 if (theme_cache.outline_size > 0 && theme_cache.outline_color.a > 0) {
1206 for (int j = 0; j < gl_size; j++) {
1207 for (int k = 0; k < glyphs[j].repeat; k++) {
1208 if ((char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) {
1209 if (glyphs[j].font_rid != RID()) {
1210 TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, theme_cache.outline_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, theme_cache.outline_color);
1211 }
1212 }
1213 char_ofs += glyphs[j].advance;
1214 }
1215 if ((char_ofs + char_margin) >= xmargin_end) {
1216 break;
1217 }
1218 }
1219 char_ofs = 0;
1220 }
1221 for (int j = 0; j < gl_size; j++) {
1222 int64_t color_start = -1;
1223 for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) {
1224 if (int64_t(*key) <= glyphs[j].start) {
1225 color_start = *key;
1226 } else {
1227 break;
1228 }
1229 }
1230 const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr;
1231 if (color_data != nullptr) {
1232 current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color);
1233 if (!editable && current_color.a > theme_cache.font_readonly_color.a) {
1234 current_color.a = theme_cache.font_readonly_color.a;
1235 }
1236 }
1237 Color gl_color = current_color;
1238
1239 for (int c = 0; c < carets.size(); c++) {
1240 if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection
1241 int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c);
1242 int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c);
1243
1244 if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && use_selected_font_color) {
1245 gl_color = theme_cache.font_selected_color;
1246 }
1247 }
1248 }
1249
1250 float char_pos = char_ofs + char_margin + ofs_x;
1251 if (char_pos >= xmargin_beg) {
1252 if (highlight_matching_braces_enabled) {
1253 for (int c = 0; c < carets.size(); c++) {
1254 if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) ||
1255 (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) {
1256 if (brace_matching[c].open_mismatch) {
1257 gl_color = _get_brace_mismatch_color();
1258 }
1259 Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
1260 draw_rect(rect, gl_color);
1261 }
1262
1263 if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) ||
1264 (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) {
1265 if (brace_matching[c].close_mismatch) {
1266 gl_color = _get_brace_mismatch_color();
1267 }
1268 Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1));
1269 draw_rect(rect, gl_color);
1270 }
1271 }
1272 }
1273
1274 if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
1275 int yofs = (text_height - theme_cache.tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
1276 theme_cache.tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
1277 } else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL)) {
1278 int yofs = (text_height - theme_cache.space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
1279 int xofs = (glyphs[j].advance * glyphs[j].repeat - theme_cache.space_icon->get_width()) / 2;
1280 theme_cache.space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);
1281 }
1282 }
1283
1284 bool had_glyphs_drawn = false;
1285 for (int k = 0; k < glyphs[j].repeat; k++) {
1286 if (!clipped && (char_ofs + char_margin) >= xmargin_beg && (char_ofs + glyphs[j].advance + char_margin) <= xmargin_end) {
1287 if (glyphs[j].font_rid != RID()) {
1288 TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
1289 had_glyphs_drawn = true;
1290 } else if (((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
1291 TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
1292 had_glyphs_drawn = true;
1293 }
1294 }
1295 char_ofs += glyphs[j].advance;
1296 }
1297
1298 if (had_glyphs_drawn) {
1299 if (first_visible_char > glyphs[j].start) {
1300 first_visible_char = glyphs[j].start;
1301 } else if (last_visible_char < glyphs[j].end) {
1302 last_visible_char = glyphs[j].end;
1303 }
1304 }
1305
1306 if ((char_ofs + char_margin) >= xmargin_end) {
1307 break;
1308 }
1309 }
1310
1311 cache_entry.first_visible_chars.push_back(first_visible_char);
1312 cache_entry.last_visible_chars.push_back(last_visible_char);
1313
1314 // is_line_folded
1315 if (line_wrap_index == line_wrap_amount && line < text.size() - 1 && _is_line_hidden(line + 1)) {
1316 int xofs = char_ofs + char_margin + ofs_x + (_get_folded_eol_icon()->get_width() / 2);
1317 if (xofs >= xmargin_beg && xofs < xmargin_end) {
1318 int yofs = (text_height - _get_folded_eol_icon()->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
1319 Color eol_color = _get_code_folding_color();
1320 eol_color.a = 1;
1321 _get_folded_eol_icon()->draw(ci, Point2(xofs, ofs_y + yofs), eol_color);
1322 }
1323 }
1324
1325 // Carets.
1326 // Prevent carets from disappearing at theme scales below 1.0 (if the caret width is 1).
1327 const int caret_width = theme_cache.caret_width * MAX(1, theme_cache.base_scale);
1328
1329 for (int c = 0; c < carets.size(); c++) {
1330 if (!clipped && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index) {
1331 carets.write[c].draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
1332
1333 if (ime_text.is_empty() || ime_selection.y == 0) {
1334 // Normal caret.
1335 CaretInfo ts_caret;
1336 if (!str.is_empty() || !ime_text.is_empty()) {
1337 // Get carets.
1338 ts_caret = TS->shaped_text_get_carets(rid, ime_text.is_empty() ? get_caret_column(c) : get_caret_column(c) + ime_selection.x);
1339 } else {
1340 // No carets, add one at the start.
1341 int h = theme_cache.font->get_height(theme_cache.font_size);
1342 if (rtl) {
1343 ts_caret.l_dir = TextServer::DIRECTION_RTL;
1344 ts_caret.l_caret = Rect2(Vector2(xmargin_end - char_margin + ofs_x, -h / 2), Size2(caret_width * 4, h));
1345 } else {
1346 ts_caret.l_dir = TextServer::DIRECTION_LTR;
1347 ts_caret.l_caret = Rect2(Vector2(char_ofs, -h / 2), Size2(caret_width * 4, h));
1348 }
1349 }
1350
1351 if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
1352 carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.l_caret.position.x;
1353 } else {
1354 carets.write[c].draw_pos.x = char_margin + ofs_x + ts_caret.t_caret.position.x;
1355 }
1356
1357 if (get_caret_draw_pos(c).x >= xmargin_beg && get_caret_draw_pos(c).x < xmargin_end) {
1358 carets.write[c].visible = true;
1359 if (draw_caret || drag_caret_force_displayed) {
1360 if (caret_type == CaretType::CARET_TYPE_BLOCK || overtype_mode) {
1361 //Block or underline caret, draw trailing carets at full height.
1362 int h = theme_cache.font->get_height(theme_cache.font_size);
1363
1364 if (ts_caret.t_caret != Rect2()) {
1365 if (overtype_mode) {
1366 ts_caret.t_caret.position.y = TS->shaped_text_get_descent(rid);
1367 ts_caret.t_caret.size.y = caret_width;
1368 } else {
1369 ts_caret.t_caret.position.y = -TS->shaped_text_get_ascent(rid);
1370 ts_caret.t_caret.size.y = h;
1371 }
1372 ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
1373 draw_rect(ts_caret.t_caret, theme_cache.caret_color, overtype_mode);
1374
1375 if (ts_caret.l_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
1376 // Draw split caret (leading part).
1377 ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
1378 ts_caret.l_caret.size.x = caret_width;
1379 draw_rect(ts_caret.l_caret, theme_cache.caret_color);
1380 // Draw extra direction marker on top of split caret.
1381 float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
1382 Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
1383 RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
1384 }
1385 } else { // End of the line.
1386 if (gl_size > 0) {
1387 // Adjust for actual line dimensions.
1388 if (overtype_mode) {
1389 ts_caret.l_caret.position.y = TS->shaped_text_get_descent(rid);
1390 ts_caret.l_caret.size.y = caret_width;
1391 } else {
1392 ts_caret.l_caret.position.y = -TS->shaped_text_get_ascent(rid);
1393 ts_caret.l_caret.size.y = h;
1394 }
1395 } else if (overtype_mode) {
1396 ts_caret.l_caret.position.y += ts_caret.l_caret.size.y;
1397 ts_caret.l_caret.size.y = caret_width;
1398 }
1399 if (Math::ceil(ts_caret.l_caret.position.x) >= TS->shaped_text_get_size(rid).x) {
1400 ts_caret.l_caret.size.x = theme_cache.font->get_char_size('m', theme_cache.font_size).x;
1401 } else {
1402 ts_caret.l_caret.size.x = 3 * caret_width;
1403 }
1404 ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
1405 if (ts_caret.l_dir == TextServer::DIRECTION_RTL) {
1406 ts_caret.l_caret.position.x -= ts_caret.l_caret.size.x;
1407 }
1408 draw_rect(ts_caret.l_caret, theme_cache.caret_color, overtype_mode);
1409 }
1410 } else {
1411 // Normal caret.
1412 if (ts_caret.l_caret != Rect2() && ts_caret.l_dir == TextServer::DIRECTION_AUTO) {
1413 // Draw extra marker on top of mid caret.
1414 Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width);
1415 trect.position += Vector2(char_margin + ofs_x, ofs_y);
1416 RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
1417 } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) {
1418 // Draw extra direction marker on top of split caret.
1419 float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
1420 Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width);
1421 trect.position += Vector2(char_margin + ofs_x, ofs_y);
1422 RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
1423
1424 d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3;
1425 trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width);
1426 trect.position += Vector2(char_margin + ofs_x, ofs_y);
1427 RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color);
1428 }
1429 ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y);
1430 ts_caret.l_caret.size.x = caret_width;
1431
1432 draw_rect(ts_caret.l_caret, theme_cache.caret_color);
1433
1434 ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y);
1435 ts_caret.t_caret.size.x = caret_width;
1436
1437 draw_rect(ts_caret.t_caret, theme_cache.caret_color);
1438 }
1439 }
1440 }
1441 }
1442 if (!ime_text.is_empty()) {
1443 {
1444 // IME Intermediate text range.
1445 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length());
1446 for (int j = 0; j < sel.size(); j++) {
1447 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
1448 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1449 continue;
1450 }
1451 if (rect.position.x < xmargin_beg) {
1452 rect.size.x -= (xmargin_beg - rect.position.x);
1453 rect.position.x = xmargin_beg;
1454 } else if (rect.position.x + rect.size.x > xmargin_end) {
1455 rect.size.x = xmargin_end - rect.position.x;
1456 }
1457 rect.size.y = caret_width;
1458 draw_rect(rect, theme_cache.caret_color);
1459 carets.write[c].draw_pos.x = rect.position.x;
1460 }
1461 }
1462 {
1463 // IME caret.
1464 Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y);
1465 for (int j = 0; j < sel.size(); j++) {
1466 Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height);
1467 if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
1468 continue;
1469 }
1470 if (rect.position.x < xmargin_beg) {
1471 rect.size.x -= (xmargin_beg - rect.position.x);
1472 rect.position.x = xmargin_beg;
1473 } else if (rect.position.x + rect.size.x > xmargin_end) {
1474 rect.size.x = xmargin_end - rect.position.x;
1475 }
1476 rect.size.y = caret_width * 3;
1477 draw_rect(rect, theme_cache.caret_color);
1478 carets.write[c].draw_pos.x = rect.position.x;
1479 }
1480 }
1481 }
1482 }
1483 }
1484 }
1485
1486 if (!draw_placeholder) {
1487 line_drawing_cache[line] = cache_entry;
1488 }
1489 }
1490
1491 if (has_focus()) {
1492 if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
1493 DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
1494 Point2 pos = get_global_position() + get_caret_draw_pos();
1495 if (get_window()->get_embedder()) {
1496 pos += get_viewport()->get_popup_base_transform().get_origin();
1497 }
1498 DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
1499 }
1500 }
1501 } break;
1502
1503 case NOTIFICATION_FOCUS_ENTER: {
1504 if (caret_blink_enabled) {
1505 caret_blink_timer->start();
1506 } else {
1507 draw_caret = true;
1508 }
1509
1510 if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
1511 DisplayServer::get_singleton()->window_set_ime_active(true, get_viewport()->get_window_id());
1512 Point2 pos = get_global_position() + get_caret_draw_pos();
1513 if (get_window()->get_embedder()) {
1514 pos += get_viewport()->get_popup_base_transform().get_origin();
1515 }
1516 DisplayServer::get_singleton()->window_set_ime_position(pos, get_viewport()->get_window_id());
1517 }
1518
1519 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
1520 int caret_start = -1;
1521 int caret_end = -1;
1522
1523 if (!has_selection(0)) {
1524 String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column());
1525
1526 caret_start = full_text.length();
1527 } else {
1528 String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column());
1529 String post_text = get_selected_text(0);
1530
1531 caret_start = pre_text.length();
1532 caret_end = caret_start + post_text.length();
1533 }
1534
1535 DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end);
1536 }
1537 } break;
1538
1539 case NOTIFICATION_FOCUS_EXIT: {
1540 if (caret_blink_enabled) {
1541 caret_blink_timer->stop();
1542 }
1543
1544 if (get_viewport()->get_window_id() != DisplayServer::INVALID_WINDOW_ID && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_IME)) {
1545 DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
1546 DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
1547 }
1548 if (!ime_text.is_empty()) {
1549 ime_text = "";
1550 ime_selection = Point2();
1551 for (int i = 0; i < carets.size(); i++) {
1552 text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, ime_text);
1553 }
1554 }
1555
1556 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
1557 DisplayServer::get_singleton()->virtual_keyboard_hide();
1558 }
1559
1560 if (deselect_on_focus_loss_enabled && !selection_drag_attempt) {
1561 deselect();
1562 }
1563 } break;
1564
1565 case MainLoop::NOTIFICATION_OS_IME_UPDATE: {
1566 if (has_focus()) {
1567 ime_text = DisplayServer::get_singleton()->ime_get_text();
1568 ime_selection = DisplayServer::get_singleton()->ime_get_selection();
1569
1570 if (!ime_text.is_empty() && has_selection()) {
1571 delete_selection();
1572 }
1573
1574 for (int i = 0; i < carets.size(); i++) {
1575 String t;
1576 if (get_caret_column(i) >= 0) {
1577 t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
1578 } else {
1579 t = ime_text;
1580 }
1581 text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
1582 }
1583 queue_redraw();
1584 }
1585 } break;
1586
1587 case NOTIFICATION_DRAG_BEGIN: {
1588 selecting_mode = SelectionMode::SELECTION_MODE_NONE;
1589 drag_action = true;
1590 dragging_minimap = false;
1591 dragging_selection = false;
1592 can_drag_minimap = false;
1593 click_select_held->stop();
1594 } break;
1595
1596 case NOTIFICATION_DRAG_END: {
1597 if (is_drag_successful()) {
1598 if (selection_drag_attempt) {
1599 selection_drag_attempt = false;
1600 if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
1601 delete_selection();
1602 } else if (deselect_on_focus_loss_enabled) {
1603 deselect();
1604 }
1605 }
1606 } else {
1607 selection_drag_attempt = false;
1608 }
1609 drag_action = false;
1610 drag_caret_force_displayed = false;
1611 } break;
1612 }
1613}
1614
1615void TextEdit::unhandled_key_input(const Ref<InputEvent> &p_event) {
1616 Ref<InputEventKey> k = p_event;
1617
1618 if (k.is_valid()) {
1619 if (!k->is_pressed()) {
1620 return;
1621 }
1622 // Handle Unicode (with modifiers active, process after shortcuts).
1623 if (has_focus() && editable && (k->get_unicode() >= 32)) {
1624 handle_unicode_input(k->get_unicode());
1625 accept_event();
1626 }
1627 }
1628}
1629
1630bool TextEdit::alt_input(const Ref<InputEvent> &p_gui_input) {
1631 Ref<InputEventKey> k = p_gui_input;
1632 if (k.is_valid()) {
1633 if (!k->is_pressed()) {
1634 if (alt_start && k->get_keycode() == Key::ALT) {
1635 alt_start = false;
1636 if ((alt_code > 0x31 && alt_code < 0xd800) || (alt_code > 0xdfff && alt_code <= 0x10ffff)) {
1637 handle_unicode_input(alt_code);
1638 }
1639 return true;
1640 }
1641 return false;
1642 }
1643
1644 if (k->is_alt_pressed()) {
1645 if (!alt_start) {
1646 if (k->get_keycode() == Key::KP_ADD) {
1647 alt_start = true;
1648 alt_code = 0;
1649 return true;
1650 }
1651 } else {
1652 if (k->get_keycode() >= Key::KEY_0 && k->get_keycode() <= Key::KEY_9) {
1653 alt_code = alt_code << 4;
1654 alt_code += (uint32_t)(k->get_keycode() - Key::KEY_0);
1655 }
1656 if (k->get_keycode() >= Key::KP_0 && k->get_keycode() <= Key::KP_9) {
1657 alt_code = alt_code << 4;
1658 alt_code += (uint32_t)(k->get_keycode() - Key::KP_0);
1659 }
1660 if (k->get_keycode() >= Key::A && k->get_keycode() <= Key::F) {
1661 alt_code = alt_code << 4;
1662 alt_code += (uint32_t)(k->get_keycode() - Key::A) + 10;
1663 }
1664 return true;
1665 }
1666 }
1667 }
1668 return false;
1669}
1670
1671void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
1672 ERR_FAIL_COND(p_gui_input.is_null());
1673
1674 double prev_v_scroll = v_scroll->get_value();
1675 double prev_h_scroll = h_scroll->get_value();
1676
1677 Ref<InputEventMouseButton> mb = p_gui_input;
1678
1679 if (mb.is_valid()) {
1680 Vector2i mpos = mb->get_position();
1681 if (is_layout_rtl()) {
1682 mpos.x = get_size().x - mpos.x;
1683 }
1684 if (ime_text.length() != 0) {
1685 // Ignore mouse clicks in IME input mode.
1686 return;
1687 }
1688
1689 if (mb->is_pressed()) {
1690 if (mb->get_button_index() == MouseButton::WHEEL_UP && !mb->is_command_or_control_pressed()) {
1691 if (mb->is_shift_pressed()) {
1692 h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
1693 } else if (mb->is_alt_pressed()) {
1694 // Scroll 5 times as fast as normal (like in Visual Studio Code).
1695 _scroll_up(15 * mb->get_factor());
1696 } else if (v_scroll->is_visible()) {
1697 // Scroll 3 lines.
1698 _scroll_up(3 * mb->get_factor());
1699 }
1700 }
1701 if (mb->get_button_index() == MouseButton::WHEEL_DOWN && !mb->is_command_or_control_pressed()) {
1702 if (mb->is_shift_pressed()) {
1703 h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
1704 } else if (mb->is_alt_pressed()) {
1705 // Scroll 5 times as fast as normal (like in Visual Studio Code).
1706 _scroll_down(15 * mb->get_factor());
1707 } else if (v_scroll->is_visible()) {
1708 // Scroll 3 lines.
1709 _scroll_down(3 * mb->get_factor());
1710 }
1711 }
1712 if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
1713 h_scroll->set_value(h_scroll->get_value() - (100 * mb->get_factor()));
1714 }
1715 if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
1716 h_scroll->set_value(h_scroll->get_value() + (100 * mb->get_factor()));
1717 }
1718 if (mb->get_button_index() == MouseButton::LEFT) {
1719 _reset_caret_blink_timer();
1720
1721 Point2i pos = get_line_column_at_pos(mpos);
1722 int row = pos.y;
1723 int col = pos.x;
1724
1725 int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
1726 for (int i = 0; i < gutters.size(); i++) {
1727 if (!gutters[i].draw || gutters[i].width <= 0) {
1728 continue;
1729 }
1730
1731 if (mpos.x >= left_margin && mpos.x <= left_margin + gutters[i].width) {
1732 emit_signal(SNAME("gutter_clicked"), row, i);
1733 return;
1734 }
1735
1736 left_margin += gutters[i].width;
1737 }
1738
1739 // Minimap
1740 if (draw_minimap) {
1741 _update_minimap_click();
1742 if (dragging_minimap) {
1743 return;
1744 }
1745 }
1746
1747 int caret = carets.size() - 1;
1748 int prev_col = get_caret_column(caret);
1749 int prev_line = get_caret_line(caret);
1750
1751 const int triple_click_timeout = 600;
1752 const int triple_click_tolerance = 5;
1753 bool is_triple_click = (!mb->is_double_click() && (OS::get_singleton()->get_ticks_msec() - last_dblclk) < triple_click_timeout && mb->get_position().distance_to(last_dblclk_pos) < triple_click_tolerance);
1754
1755 if (!mb->is_double_click() && !is_triple_click) {
1756 if (mb->is_alt_pressed()) {
1757 prev_line = row;
1758 prev_col = col;
1759
1760 // Remove caret at clicked location.
1761 if (carets.size() > 1) {
1762 for (int i = 0; i < carets.size(); i++) {
1763 // Deselect if clicked on caret or its selection.
1764 if ((get_caret_column(i) == col && get_caret_line(i) == row) || is_mouse_over_selection(true, i)) {
1765 remove_caret(i);
1766 last_dblclk = 0;
1767 return;
1768 }
1769 }
1770 }
1771
1772 if (is_mouse_over_selection()) {
1773 return;
1774 }
1775
1776 caret = add_caret(row, col);
1777 if (caret == -1) {
1778 return;
1779 }
1780
1781 carets.write[caret].selection.selecting_line = row;
1782 carets.write[caret].selection.selecting_column = col;
1783
1784 last_dblclk = 0;
1785 } else if (!mb->is_shift_pressed() && !is_mouse_over_selection()) {
1786 caret = 0;
1787 remove_secondary_carets();
1788 }
1789 }
1790
1791 set_caret_line(row, false, true, 0, caret);
1792 set_caret_column(col, false, caret);
1793 selection_drag_attempt = false;
1794
1795 if (selecting_enabled && mb->is_shift_pressed() && (get_caret_column(caret) != prev_col || get_caret_line(caret) != prev_line)) {
1796 if (!has_selection(caret)) {
1797 carets.write[caret].selection.active = true;
1798 selecting_mode = SelectionMode::SELECTION_MODE_POINTER;
1799 carets.write[caret].selection.from_column = prev_col;
1800 carets.write[caret].selection.from_line = prev_line;
1801 carets.write[caret].selection.to_column = carets[caret].column;
1802 carets.write[caret].selection.to_line = carets[caret].line;
1803
1804 if (get_selection_from_line(caret) > get_selection_to_line(caret) || (get_selection_from_line(caret) == get_selection_to_line(caret) && get_selection_from_column(caret) > get_selection_to_column(caret))) {
1805 SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
1806 SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
1807 carets.write[caret].selection.shiftclick_left = false;
1808 } else {
1809 carets.write[caret].selection.shiftclick_left = true;
1810 }
1811 carets.write[caret].selection.selecting_line = prev_line;
1812 carets.write[caret].selection.selecting_column = prev_col;
1813 caret_index_edit_dirty = true;
1814 merge_overlapping_carets();
1815 queue_redraw();
1816 } else {
1817 if (carets[caret].line < get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column < get_selection_column(caret))) {
1818 if (carets[caret].selection.shiftclick_left) {
1819 carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
1820 }
1821 carets.write[caret].selection.from_column = carets[caret].column;
1822 carets.write[caret].selection.from_line = carets[caret].line;
1823
1824 } else if (carets[caret].line > get_selection_line(caret) || (carets[caret].line == get_selection_line(caret) && carets[caret].column > get_selection_column(caret))) {
1825 if (!carets[caret].selection.shiftclick_left) {
1826 SWAP(carets.write[caret].selection.from_column, carets.write[caret].selection.to_column);
1827 SWAP(carets.write[caret].selection.from_line, carets.write[caret].selection.to_line);
1828 carets.write[caret].selection.shiftclick_left = !carets[caret].selection.shiftclick_left;
1829 }
1830 carets.write[caret].selection.to_column = carets[caret].column;
1831 carets.write[caret].selection.to_line = carets[caret].line;
1832
1833 } else {
1834 deselect(caret);
1835 }
1836 caret_index_edit_dirty = true;
1837 merge_overlapping_carets();
1838 queue_redraw();
1839 }
1840 } else if (drag_and_drop_selection_enabled && is_mouse_over_selection()) {
1841 set_selection_mode(SelectionMode::SELECTION_MODE_NONE, get_selection_line(caret), get_selection_column(caret), caret);
1842 // We use the main caret for dragging, so reset this one.
1843 set_caret_line(prev_line, false, true, 0, caret);
1844 set_caret_column(prev_col, false, caret);
1845 selection_drag_attempt = true;
1846 } else if (caret == 0) {
1847 deselect();
1848 set_selection_mode(SelectionMode::SELECTION_MODE_POINTER, row, col);
1849 }
1850
1851 if (is_triple_click) {
1852 // Triple-click select line.
1853 selecting_mode = SelectionMode::SELECTION_MODE_LINE;
1854 selection_drag_attempt = false;
1855 _update_selection_mode_line();
1856 last_dblclk = 0;
1857 } else if (mb->is_double_click() && text[get_caret_line(caret)].length()) {
1858 // Double-click select word.
1859 selecting_mode = SelectionMode::SELECTION_MODE_WORD;
1860 _update_selection_mode_word();
1861 last_dblclk = OS::get_singleton()->get_ticks_msec();
1862 last_dblclk_pos = mb->get_position();
1863 }
1864 queue_redraw();
1865 }
1866
1867 if (is_middle_mouse_paste_enabled() && mb->get_button_index() == MouseButton::MIDDLE && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
1868 paste_primary_clipboard();
1869 }
1870
1871 if (mb->get_button_index() == MouseButton::RIGHT && (context_menu_enabled || is_move_caret_on_right_click_enabled())) {
1872 _reset_caret_blink_timer();
1873
1874 Point2i pos = get_line_column_at_pos(mpos);
1875 int row = pos.y;
1876 int col = pos.x;
1877
1878 bool selection_clicked = false;
1879 if (is_move_caret_on_right_click_enabled()) {
1880 if (has_selection()) {
1881 for (int i = 0; i < get_caret_count(); i++) {
1882 int from_line = get_selection_from_line(i);
1883 int to_line = get_selection_to_line(i);
1884 int from_column = get_selection_from_column(i);
1885 int to_column = get_selection_to_column(i);
1886
1887 if (row >= from_line && row <= to_line && (row != from_line || col >= from_column) && (row != to_line || col <= to_column)) {
1888 // Right click in one of the selected text
1889 selection_clicked = true;
1890 break;
1891 }
1892 }
1893 }
1894 if (!selection_clicked) {
1895 deselect();
1896 remove_secondary_carets();
1897 set_caret_line(row, false, false);
1898 set_caret_column(col);
1899 }
1900 merge_overlapping_carets();
1901 }
1902
1903 if (context_menu_enabled) {
1904 _update_context_menu();
1905 menu->set_position(get_screen_position() + mpos);
1906 menu->reset_size();
1907 menu->popup();
1908 grab_focus();
1909 }
1910 }
1911 } else {
1912 if (mb->get_button_index() == MouseButton::LEFT) {
1913 if (selection_drag_attempt && is_mouse_over_selection()) {
1914 remove_secondary_carets();
1915
1916 Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
1917 set_caret_line(pos.y, false, true, 0, 0);
1918 set_caret_column(pos.x, true, 0);
1919
1920 deselect();
1921 }
1922 dragging_minimap = false;
1923 dragging_selection = false;
1924 can_drag_minimap = false;
1925 click_select_held->stop();
1926 if (!drag_action) {
1927 selection_drag_attempt = false;
1928 }
1929 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
1930 DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
1931 }
1932 }
1933
1934 // Notify to show soft keyboard.
1935 notification(NOTIFICATION_FOCUS_ENTER);
1936 }
1937 }
1938
1939 const Ref<InputEventPanGesture> pan_gesture = p_gui_input;
1940 if (pan_gesture.is_valid()) {
1941 const real_t delta = pan_gesture->get_delta().y;
1942 if (delta < 0) {
1943 _scroll_up(-delta);
1944 } else {
1945 _scroll_down(delta);
1946 }
1947 h_scroll->set_value(h_scroll->get_value() + pan_gesture->get_delta().x * 100);
1948 if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
1949 accept_event(); // Accept event if scroll changed.
1950 }
1951
1952 return;
1953 }
1954
1955 Ref<InputEventMouseMotion> mm = p_gui_input;
1956
1957 if (mm.is_valid()) {
1958 Vector2i mpos = mm->get_position();
1959 if (is_layout_rtl()) {
1960 mpos.x = get_size().x - mpos.x;
1961 }
1962
1963 if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT) && get_viewport()->gui_get_drag_data() == Variant()) { // Ignore if dragging.
1964 _reset_caret_blink_timer();
1965
1966 if (draw_minimap && !dragging_selection) {
1967 _update_minimap_drag();
1968 }
1969
1970 if (!dragging_minimap) {
1971 switch (selecting_mode) {
1972 case SelectionMode::SELECTION_MODE_POINTER: {
1973 _update_selection_mode_pointer();
1974 } break;
1975 case SelectionMode::SELECTION_MODE_WORD: {
1976 _update_selection_mode_word();
1977 } break;
1978 case SelectionMode::SELECTION_MODE_LINE: {
1979 _update_selection_mode_line();
1980 } break;
1981 default: {
1982 break;
1983 }
1984 }
1985 }
1986 }
1987
1988 // Check if user is hovering a different gutter, and update if yes.
1989 Vector2i current_hovered_gutter = Vector2i(-1, -1);
1990
1991 int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
1992 if (mpos.x <= left_margin + gutters_width + gutter_padding) {
1993 int hovered_row = get_line_column_at_pos(mpos).y;
1994 for (int i = 0; i < gutters.size(); i++) {
1995 if (!gutters[i].draw || gutters[i].width <= 0) {
1996 continue;
1997 }
1998
1999 if (mpos.x >= left_margin && mpos.x < left_margin + gutters[i].width) {
2000 // We are in this gutter i's horizontal area.
2001 current_hovered_gutter = Vector2i(i, hovered_row);
2002 break;
2003 }
2004
2005 left_margin += gutters[i].width;
2006 }
2007 }
2008
2009 if (current_hovered_gutter != hovered_gutter) {
2010 hovered_gutter = current_hovered_gutter;
2011 queue_redraw();
2012 }
2013
2014 if (drag_action && can_drop_data(mpos, get_viewport()->gui_get_drag_data())) {
2015 drag_caret_force_displayed = true;
2016 Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
2017 set_caret_line(pos.y, false, true, 0, 0);
2018 set_caret_column(pos.x, true, 0);
2019 dragging_selection = true;
2020 }
2021 }
2022
2023 if (draw_minimap && !dragging_selection) {
2024 _update_minimap_hover();
2025 }
2026
2027 if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
2028 accept_event(); // Accept event if scroll changed.
2029 }
2030
2031 Ref<InputEventKey> k = p_gui_input;
2032
2033 if (k.is_valid()) {
2034 if (alt_input(p_gui_input)) {
2035 accept_event();
2036 return;
2037 }
2038 if (!k->is_pressed()) {
2039 return;
2040 }
2041
2042 // If a modifier has been pressed, and nothing else, return.
2043 if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT || k->get_keycode() == Key::META || k->get_keycode() == Key::CAPSLOCK) {
2044 return;
2045 }
2046
2047 _reset_caret_blink_timer();
2048
2049 // Allow unicode handling if:
2050 // * No modifiers are pressed (except Shift and CapsLock)
2051 bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
2052
2053 // Check and handle all built-in shortcuts.
2054
2055 // NEWLINES.
2056 if (k->is_action("ui_text_newline_above", true)) {
2057 _new_line(false, true);
2058 accept_event();
2059 return;
2060 }
2061 if (k->is_action("ui_text_newline_blank", true)) {
2062 _new_line(false);
2063 accept_event();
2064 return;
2065 }
2066 if (k->is_action("ui_text_newline", true)) {
2067 _new_line();
2068 accept_event();
2069 return;
2070 }
2071
2072 // BACKSPACE AND DELETE.
2073 if (k->is_action("ui_text_backspace_all_to_left", true)) {
2074 _do_backspace(false, true);
2075 accept_event();
2076 return;
2077 }
2078 if (k->is_action("ui_text_backspace_word", true)) {
2079 _do_backspace(true);
2080 accept_event();
2081 return;
2082 }
2083 if (k->is_action("ui_text_backspace", true)) {
2084 _do_backspace();
2085 accept_event();
2086 return;
2087 }
2088 if (k->is_action("ui_text_delete_all_to_right", true)) {
2089 _delete(false, true);
2090 accept_event();
2091 return;
2092 }
2093 if (k->is_action("ui_text_delete_word", true)) {
2094 _delete(true);
2095 accept_event();
2096 return;
2097 }
2098 if (k->is_action("ui_text_delete", true)) {
2099 _delete();
2100 accept_event();
2101 return;
2102 }
2103
2104 // SCROLLING.
2105 if (k->is_action("ui_text_scroll_up", true)) {
2106 _scroll_lines_up();
2107 accept_event();
2108 return;
2109 }
2110 if (k->is_action("ui_text_scroll_down", true)) {
2111 _scroll_lines_down();
2112 accept_event();
2113 return;
2114 }
2115
2116 if (is_shortcut_keys_enabled()) {
2117 // SELECT ALL, SELECT WORD UNDER CARET, ADD SELECTION FOR NEXT OCCURRENCE,
2118 // CLEAR CARETS AND SELECTIONS, CUT, COPY, PASTE.
2119 if (k->is_action("ui_text_select_all", true)) {
2120 select_all();
2121 accept_event();
2122 return;
2123 }
2124 if (k->is_action("ui_text_select_word_under_caret", true)) {
2125 select_word_under_caret();
2126 accept_event();
2127 return;
2128 }
2129 if (k->is_action("ui_text_add_selection_for_next_occurrence", true)) {
2130 add_selection_for_next_occurrence();
2131 accept_event();
2132 return;
2133 }
2134 if (k->is_action("ui_text_clear_carets_and_selection", true)) {
2135 // Since the default shortcut is ESC, accepts the event only if it's actually performed.
2136 if (_clear_carets_and_selection()) {
2137 accept_event();
2138 return;
2139 }
2140 }
2141 if (k->is_action("ui_cut", true)) {
2142 cut();
2143 accept_event();
2144 return;
2145 }
2146 if (k->is_action("ui_copy", true)) {
2147 copy();
2148 accept_event();
2149 return;
2150 }
2151 if (k->is_action("ui_paste", true)) {
2152 paste();
2153 accept_event();
2154 return;
2155 }
2156
2157 // UNDO/REDO.
2158 if (k->is_action("ui_undo", true)) {
2159 undo();
2160 accept_event();
2161 return;
2162 }
2163 if (k->is_action("ui_redo", true)) {
2164 redo();
2165 accept_event();
2166 return;
2167 }
2168
2169 if (k->is_action("ui_text_caret_add_below", true)) {
2170 add_caret_at_carets(true);
2171 accept_event();
2172 return;
2173 }
2174 if (k->is_action("ui_text_caret_add_above", true)) {
2175 add_caret_at_carets(false);
2176 accept_event();
2177 return;
2178 }
2179 }
2180
2181 // MISC.
2182 if (k->is_action("ui_menu", true)) {
2183 if (context_menu_enabled) {
2184 _update_context_menu();
2185 adjust_viewport_to_caret();
2186 menu->set_position(get_screen_position() + get_caret_draw_pos());
2187 menu->reset_size();
2188 menu->popup();
2189 menu->grab_focus();
2190 }
2191 accept_event();
2192 return;
2193 }
2194 if (k->is_action("ui_text_toggle_insert_mode", true)) {
2195 set_overtype_mode_enabled(!overtype_mode);
2196 accept_event();
2197 return;
2198 }
2199 if (k->is_action("ui_swap_input_direction", true)) {
2200 _swap_current_input_direction();
2201 accept_event();
2202 return;
2203 }
2204
2205 // CARET MOVEMENT
2206
2207 k = k->duplicate();
2208 bool shift_pressed = k->is_shift_pressed();
2209 // Remove shift or else actions will not match. Use above variable for selection.
2210 k->set_shift_pressed(false);
2211
2212 // CARET MOVEMENT - LEFT, RIGHT.
2213 if (k->is_action("ui_text_caret_word_left", true)) {
2214 _move_caret_left(shift_pressed, true);
2215 accept_event();
2216 return;
2217 }
2218 if (k->is_action("ui_text_caret_left", true)) {
2219 _move_caret_left(shift_pressed, false);
2220 accept_event();
2221 return;
2222 }
2223 if (k->is_action("ui_text_caret_word_right", true)) {
2224 _move_caret_right(shift_pressed, true);
2225 accept_event();
2226 return;
2227 }
2228 if (k->is_action("ui_text_caret_right", true)) {
2229 _move_caret_right(shift_pressed, false);
2230 accept_event();
2231 return;
2232 }
2233
2234 // CARET MOVEMENT - UP, DOWN.
2235 if (k->is_action("ui_text_caret_up", true)) {
2236 _move_caret_up(shift_pressed);
2237 accept_event();
2238 return;
2239 }
2240 if (k->is_action("ui_text_caret_down", true)) {
2241 _move_caret_down(shift_pressed);
2242 accept_event();
2243 return;
2244 }
2245
2246 // CARET MOVEMENT - DOCUMENT START/END.
2247 if (k->is_action("ui_text_caret_document_start", true)) { // && shift_pressed) {
2248 _move_caret_document_start(shift_pressed);
2249 accept_event();
2250 return;
2251 }
2252 if (k->is_action("ui_text_caret_document_end", true)) { // && shift_pressed) {
2253 _move_caret_document_end(shift_pressed);
2254 accept_event();
2255 return;
2256 }
2257
2258 // CARET MOVEMENT - LINE START/END.
2259 if (k->is_action("ui_text_caret_line_start", true)) {
2260 _move_caret_to_line_start(shift_pressed);
2261 accept_event();
2262 return;
2263 }
2264 if (k->is_action("ui_text_caret_line_end", true)) {
2265 _move_caret_to_line_end(shift_pressed);
2266 accept_event();
2267 return;
2268 }
2269
2270 // CARET MOVEMENT - PAGE UP/DOWN.
2271 if (k->is_action("ui_text_caret_page_up", true)) {
2272 _move_caret_page_up(shift_pressed);
2273 accept_event();
2274 return;
2275 }
2276 if (k->is_action("ui_text_caret_page_down", true)) {
2277 _move_caret_page_down(shift_pressed);
2278 accept_event();
2279 return;
2280 }
2281
2282 // Handle tab as it has no set unicode value.
2283 if (k->is_action("ui_text_indent", true)) {
2284 if (editable) {
2285 insert_text_at_caret("\t");
2286 }
2287 accept_event();
2288 return;
2289 }
2290
2291 // Handle Unicode (if no modifiers active).
2292 if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
2293 handle_unicode_input(k->get_unicode());
2294 accept_event();
2295 return;
2296 }
2297 }
2298}
2299
2300/* Input actions. */
2301void TextEdit::_swap_current_input_direction() {
2302 if (input_direction == TEXT_DIRECTION_LTR) {
2303 input_direction = TEXT_DIRECTION_RTL;
2304 } else {
2305 input_direction = TEXT_DIRECTION_LTR;
2306 }
2307 for (int i = 0; i < carets.size(); i++) {
2308 set_caret_column(get_caret_column(i), i == 0, i);
2309 }
2310 queue_redraw();
2311}
2312
2313void TextEdit::_new_line(bool p_split_current_line, bool p_above) {
2314 if (!editable) {
2315 return;
2316 }
2317
2318 begin_complex_operation();
2319 Vector<int> caret_edit_order = get_caret_index_edit_order();
2320 for (const int &i : caret_edit_order) {
2321 bool first_line = false;
2322 if (!p_split_current_line) {
2323 deselect(i);
2324 if (p_above) {
2325 if (get_caret_line(i) > 0) {
2326 set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
2327 set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
2328 } else {
2329 set_caret_column(0, i == 0, i);
2330 first_line = true;
2331 }
2332 } else {
2333 set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
2334 }
2335 }
2336
2337 insert_text_at_caret("\n", i);
2338
2339 if (first_line) {
2340 set_caret_line(0, i == 0, true, 0, i);
2341 }
2342 }
2343 end_complex_operation();
2344}
2345
2346void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
2347 for (int i = 0; i < carets.size(); i++) {
2348 // Handle selection.
2349 if (p_select) {
2350 _pre_shift_selection(i);
2351 } else if (has_selection(i) && !p_move_by_word) {
2352 // If a selection is active, move caret to start of selection.
2353 set_caret_line(get_selection_from_line(i), false, true, 0, i);
2354 set_caret_column(get_selection_from_column(i), i == 0, i);
2355 deselect(i);
2356 continue;
2357 } else {
2358 deselect(i);
2359 }
2360
2361 if (p_move_by_word) {
2362 int cc = get_caret_column(i);
2363 // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
2364 if (cc == 0 && get_caret_line(i) > 0) {
2365 set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
2366 set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
2367 } else {
2368 PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
2369 if (words.is_empty() || cc <= words[0]) {
2370 // This solves the scenario where there are no words but glyfs that can be ignored.
2371 cc = 0;
2372 } else {
2373 for (int j = words.size() - 2; j >= 0; j = j - 2) {
2374 if (words[j] < cc) {
2375 cc = words[j];
2376 break;
2377 }
2378 }
2379 }
2380 set_caret_column(cc, i == 0, i);
2381 }
2382 } else {
2383 // If the caret is at the start of the line, and not on the first line, move it up to the end of the previous line.
2384 if (get_caret_column(i) == 0) {
2385 if (get_caret_line(i) > 0) {
2386 set_caret_line(get_caret_line(i) - get_next_visible_line_offset_from(CLAMP(get_caret_line(i) - 1, 0, text.size() - 1), -1), false, true, 0, i);
2387 set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
2388 }
2389 } else {
2390 if (caret_mid_grapheme_enabled) {
2391 set_caret_column(get_caret_column(i) - 1, i == 0, i);
2392 } else {
2393 set_caret_column(TS->shaped_text_prev_character_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
2394 }
2395 }
2396 }
2397
2398 if (p_select) {
2399 _post_shift_selection(i);
2400 }
2401 }
2402 merge_overlapping_carets();
2403}
2404
2405void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
2406 for (int i = 0; i < carets.size(); i++) {
2407 // Handle selection.
2408 if (p_select) {
2409 _pre_shift_selection(i);
2410 } else if (has_selection(i) && !p_move_by_word) {
2411 // If a selection is active, move caret to end of selection.
2412 set_caret_line(get_selection_to_line(i), false, true, 0, i);
2413 set_caret_column(get_selection_to_column(i), i == 0, i);
2414 deselect(i);
2415 continue;
2416 } else {
2417 deselect(i);
2418 }
2419
2420 if (p_move_by_word) {
2421 int cc = get_caret_column(i);
2422 // If the caret is at the end of the line, and not on the last line, move it down to the beginning of the next line.
2423 if (cc == text[get_caret_line(i)].length() && get_caret_line(i) < text.size() - 1) {
2424 set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
2425 set_caret_column(0, i == 0, i);
2426 } else {
2427 PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid());
2428 if (words.is_empty() || cc >= words[words.size() - 1]) {
2429 // This solves the scenario where there are no words but glyfs that can be ignored.
2430 cc = text[get_caret_line(i)].length();
2431 } else {
2432 for (int j = 1; j < words.size(); j = j + 2) {
2433 if (words[j] > cc) {
2434 cc = words[j];
2435 break;
2436 }
2437 }
2438 }
2439 set_caret_column(cc, i == 0, i);
2440 }
2441 } else {
2442 // If we are at the end of the line, move the caret to the next line down.
2443 if (get_caret_column(i) == text[get_caret_line(i)].length()) {
2444 if (get_caret_line(i) < text.size() - 1) {
2445 set_caret_line(get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1), false, false, 0, i);
2446 set_caret_column(0, i == 0, i);
2447 }
2448 } else {
2449 if (caret_mid_grapheme_enabled) {
2450 set_caret_column(get_caret_column(i) + 1, i == 0, i);
2451 } else {
2452 set_caret_column(TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
2453 }
2454 }
2455 }
2456
2457 if (p_select) {
2458 _post_shift_selection(i);
2459 }
2460 }
2461 merge_overlapping_carets();
2462}
2463
2464void TextEdit::_move_caret_up(bool p_select) {
2465 for (int i = 0; i < carets.size(); i++) {
2466 if (p_select) {
2467 _pre_shift_selection(i);
2468 } else {
2469 deselect(i);
2470 }
2471
2472 int cur_wrap_index = get_caret_wrap_index(i);
2473 if (cur_wrap_index > 0) {
2474 set_caret_line(get_caret_line(i), true, false, cur_wrap_index - 1, i);
2475 } else if (get_caret_line(i) == 0) {
2476 set_caret_column(0, i == 0, i);
2477 } else {
2478 int new_line = get_caret_line(i) - get_next_visible_line_offset_from(get_caret_line(i) - 1, -1);
2479 if (is_line_wrapped(new_line)) {
2480 set_caret_line(new_line, i == 0, false, get_line_wrap_count(new_line), i);
2481 } else {
2482 set_caret_line(new_line, i == 0, false, 0, i);
2483 }
2484 }
2485
2486 if (p_select) {
2487 _post_shift_selection(i);
2488 }
2489 }
2490 merge_overlapping_carets();
2491}
2492
2493void TextEdit::_move_caret_down(bool p_select) {
2494 for (int i = 0; i < carets.size(); i++) {
2495 if (p_select) {
2496 _pre_shift_selection(i);
2497 } else {
2498 deselect(i);
2499 }
2500
2501 int cur_wrap_index = get_caret_wrap_index(i);
2502 if (cur_wrap_index < get_line_wrap_count(get_caret_line(i))) {
2503 set_caret_line(get_caret_line(i), i == 0, false, cur_wrap_index + 1, i);
2504 } else if (get_caret_line(i) == get_last_unhidden_line()) {
2505 set_caret_column(text[get_caret_line(i)].length());
2506 } else {
2507 int new_line = get_caret_line(i) + get_next_visible_line_offset_from(CLAMP(get_caret_line(i) + 1, 0, text.size() - 1), 1);
2508 set_caret_line(new_line, i == 0, false, 0, i);
2509 }
2510
2511 if (p_select) {
2512 _post_shift_selection(i);
2513 }
2514 }
2515 merge_overlapping_carets();
2516}
2517
2518void TextEdit::_move_caret_to_line_start(bool p_select) {
2519 for (int i = 0; i < carets.size(); i++) {
2520 if (p_select) {
2521 _pre_shift_selection(i);
2522 } else {
2523 deselect(i);
2524 }
2525
2526 // Move caret column to start of wrapped row and then to start of text.
2527 Vector<String> rows = get_line_wrapped_text(get_caret_line(i));
2528 int wi = get_caret_wrap_index(i);
2529 int row_start_col = 0;
2530 for (int j = 0; j < wi; j++) {
2531 row_start_col += rows[j].length();
2532 }
2533 if (get_caret_column(i) == row_start_col || wi == 0) {
2534 // Compute whitespace symbols sequence length.
2535 int current_line_whitespace_len = get_first_non_whitespace_column(get_caret_line(i));
2536 if (get_caret_column(i) == current_line_whitespace_len) {
2537 set_caret_column(0, i == 0, i);
2538 } else {
2539 set_caret_column(current_line_whitespace_len, i == 0, i);
2540 }
2541 } else {
2542 set_caret_column(row_start_col, i == 0, i);
2543 }
2544
2545 if (p_select) {
2546 _post_shift_selection(i);
2547 }
2548 }
2549 merge_overlapping_carets();
2550}
2551
2552void TextEdit::_move_caret_to_line_end(bool p_select) {
2553 for (int i = 0; i < carets.size(); i++) {
2554 if (p_select) {
2555 _pre_shift_selection(i);
2556 } else {
2557 deselect(i);
2558 }
2559
2560 // Move caret column to end of wrapped row and then to end of text.
2561 Vector<String> rows = get_line_wrapped_text(get_caret_line(i));
2562 int wi = get_caret_wrap_index(i);
2563 int row_end_col = -1;
2564 for (int j = 0; j < wi + 1; j++) {
2565 row_end_col += rows[j].length();
2566 }
2567 if (wi == rows.size() - 1 || get_caret_column(i) == row_end_col) {
2568 set_caret_column(text[get_caret_line(i)].length(), i == 0, i);
2569 } else {
2570 set_caret_column(row_end_col, i == 0, i);
2571 }
2572
2573 carets.write[i].last_fit_x = INT_MAX;
2574
2575 if (p_select) {
2576 _post_shift_selection(i);
2577 }
2578 }
2579 merge_overlapping_carets();
2580}
2581
2582void TextEdit::_move_caret_page_up(bool p_select) {
2583 for (int i = 0; i < carets.size(); i++) {
2584 if (p_select) {
2585 _pre_shift_selection(i);
2586 } else {
2587 deselect(i);
2588 }
2589
2590 Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), -get_visible_line_count());
2591 int n_line = get_caret_line(i) - next_line.x + 1;
2592 set_caret_line(n_line, i == 0, false, next_line.y, i);
2593
2594 if (p_select) {
2595 _post_shift_selection(i);
2596 }
2597 }
2598 merge_overlapping_carets();
2599}
2600
2601void TextEdit::_move_caret_page_down(bool p_select) {
2602 for (int i = 0; i < carets.size(); i++) {
2603 if (p_select) {
2604 _pre_shift_selection(i);
2605 } else {
2606 deselect(i);
2607 }
2608
2609 Point2i next_line = get_next_visible_line_index_offset_from(get_caret_line(i), get_caret_wrap_index(i), get_visible_line_count());
2610 int n_line = get_caret_line(i) + next_line.x - 1;
2611 set_caret_line(n_line, i == 0, false, next_line.y, i);
2612
2613 if (p_select) {
2614 _post_shift_selection(i);
2615 }
2616 }
2617 merge_overlapping_carets();
2618}
2619
2620void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) {
2621 if (!editable) {
2622 return;
2623 }
2624
2625 start_action(EditAction::ACTION_BACKSPACE);
2626 Vector<int> carets_to_remove;
2627
2628 Vector<int> caret_edit_order = get_caret_index_edit_order();
2629 for (int i = 0; i < caret_edit_order.size(); i++) {
2630 int caret_idx = caret_edit_order[i];
2631 if (get_caret_column(caret_idx) == 0 && get_caret_line(caret_idx) == 0 && !has_selection(caret_idx)) {
2632 continue;
2633 }
2634
2635 if (has_selection(caret_idx) || (!p_all_to_left && !p_word) || get_caret_column(caret_idx) == 0) {
2636 backspace(caret_idx);
2637 continue;
2638 }
2639
2640 if (p_all_to_left) {
2641 int caret_current_column = get_caret_column(caret_idx);
2642 set_caret_column(0, caret_idx == 0, caret_idx);
2643 _remove_text(get_caret_line(caret_idx), 0, get_caret_line(caret_idx), caret_current_column);
2644 adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), caret_current_column, get_caret_line(caret_idx), get_caret_column(caret_idx));
2645
2646 // Check for any overlapping carets since we removed the entire line.
2647 for (int j = i + 1; j < caret_edit_order.size(); j++) {
2648 // Selection only end on this line, only the one as carets cannot overlap.
2649 if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx) && get_selection_to_line(caret_edit_order[j]) == get_caret_line(caret_idx)) {
2650 carets.write[caret_edit_order[j]].selection.to_column = 0;
2651 break;
2652 }
2653
2654 // Check for caret.
2655 if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) || (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
2656 break;
2657 }
2658
2659 deselect(caret_edit_order[j]);
2660 carets_to_remove.push_back(caret_edit_order[j]);
2661 set_caret_column(0, caret_idx == 0, caret_idx);
2662 i = j;
2663 }
2664 continue;
2665 }
2666
2667 if (p_word) {
2668 // Save here as the caret may change when resolving overlaps.
2669 int from_column = get_caret_column(caret_idx);
2670 int column = get_caret_column(caret_idx);
2671 // Check for the case "<word><space><caret>" and ignore the space.
2672 // No need to check for column being 0 since it is checked above.
2673 if (is_whitespace(text[get_caret_line(caret_idx)][get_caret_column(caret_idx) - 1])) {
2674 column -= 1;
2675 }
2676 // Get a list with the indices of the word bounds of the given text line.
2677 const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_idx))->get_rid());
2678 if (words.is_empty() || column <= words[0]) {
2679 // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line.
2680 column = 0;
2681 } else {
2682 // Otherwise search for the first word break that is smaller than the index from we're currently deleting.
2683 for (int c = words.size() - 2; c >= 0; c = c - 2) {
2684 if (words[c] < column) {
2685 column = words[c];
2686 break;
2687 }
2688 }
2689 }
2690
2691 // Check for any other carets in this range.
2692 int overlapping_caret_index = -1;
2693 for (int j = i + 1; j < caret_edit_order.size(); j++) {
2694 // Check caret and selection in on the right line.
2695 if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx) && (!has_selection(caret_edit_order[j]) || get_selection_to_line(caret_edit_order[j]) != get_caret_line(caret_idx))) {
2696 break;
2697 }
2698
2699 // If it has a selection, check it ends with in the range.
2700 if ((has_selection(caret_edit_order[j]) && get_selection_to_column(caret_edit_order[j]) < column)) {
2701 break;
2702 }
2703
2704 // If it has a selection and it starts outside our word, we need to adjust the selection, and handle it later to prevent overlap.
2705 if ((has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) < column)) {
2706 carets.write[caret_edit_order[j]].selection.to_column = column;
2707 overlapping_caret_index = caret_edit_order[j];
2708 break;
2709 }
2710
2711 // Otherwise we can remove it.
2712 if (get_caret_column(caret_edit_order[j]) > column || (has_selection(caret_edit_order[j]) && get_selection_from_column(caret_edit_order[j]) > column)) {
2713 deselect(caret_edit_order[j]);
2714 carets_to_remove.push_back(caret_edit_order[j]);
2715 set_caret_column(0, caret_idx == 0, caret_idx);
2716 i = j;
2717 }
2718 }
2719
2720 _remove_text(get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
2721
2722 set_caret_line(get_caret_line(caret_idx), false, true, 0, caret_idx);
2723 set_caret_column(column, caret_idx == 0, caret_idx);
2724 adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), column, get_caret_line(caret_idx), from_column);
2725
2726 // Now we can clean up the overlapping caret.
2727 if (overlapping_caret_index != -1) {
2728 backspace(overlapping_caret_index);
2729 i++;
2730 carets_to_remove.push_back(overlapping_caret_index);
2731 set_caret_column(get_caret_column(overlapping_caret_index), caret_idx == 0, caret_idx);
2732 }
2733 continue;
2734 }
2735 }
2736
2737 // Sort and remove backwards to preserve indexes.
2738 carets_to_remove.sort();
2739 for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
2740 remove_caret(carets_to_remove[i]);
2741 }
2742 end_action();
2743}
2744
2745void TextEdit::_delete(bool p_word, bool p_all_to_right) {
2746 if (!editable) {
2747 return;
2748 }
2749
2750 start_action(EditAction::ACTION_DELETE);
2751 Vector<int> carets_to_remove;
2752
2753 Vector<int> caret_edit_order = get_caret_index_edit_order();
2754 for (int i = 0; i < caret_edit_order.size(); i++) {
2755 int caret_idx = caret_edit_order[i];
2756 if (has_selection(caret_idx)) {
2757 delete_selection(caret_idx);
2758 continue;
2759 }
2760 int curline_len = text[get_caret_line(caret_idx)].length();
2761
2762 if (get_caret_line(caret_idx) == text.size() - 1 && get_caret_column(caret_idx) == curline_len) {
2763 continue; // Last line, last column: Nothing to do.
2764 }
2765
2766 int next_line = get_caret_column(caret_idx) < curline_len ? get_caret_line(caret_idx) : get_caret_line(caret_idx) + 1;
2767 int next_column;
2768
2769 if (p_all_to_right) {
2770 // Get caret furthest to the left.
2771 for (int j = i + 1; j < caret_edit_order.size(); j++) {
2772 if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
2773 break;
2774 }
2775
2776 if (has_selection(caret_edit_order[j]) && get_selection_from_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
2777 break;
2778 }
2779
2780 if (!has_selection(caret_edit_order[j])) {
2781 i = j;
2782 caret_idx = caret_edit_order[i];
2783 }
2784 }
2785
2786 if (get_caret_column(caret_idx) == curline_len) {
2787 continue;
2788 }
2789
2790 // Delete everything to right of caret.
2791 next_column = curline_len;
2792 next_line = get_caret_line(caret_idx);
2793
2794 // Remove overlapping carets.
2795 for (int j = i - 1; j >= 0; j--) {
2796 if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
2797 break;
2798 }
2799 carets_to_remove.push_back(caret_edit_order[j]);
2800 }
2801
2802 } else if (p_word && get_caret_column(caret_idx) < curline_len - 1) {
2803 // Delete next word to right of caret.
2804 int line = get_caret_line(caret_idx);
2805 int column = get_caret_column(caret_idx);
2806
2807 PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
2808 for (int j = 1; j < words.size(); j = j + 2) {
2809 if (words[j] > column) {
2810 column = words[j];
2811 break;
2812 }
2813 }
2814
2815 next_line = line;
2816 next_column = column;
2817
2818 // Remove overlapping carets.
2819 for (int j = i - 1; j >= 0; j--) {
2820 if (get_caret_line(caret_edit_order[j]) != get_caret_line(caret_idx)) {
2821 break;
2822 }
2823
2824 if (get_caret_column(caret_edit_order[j]) > column) {
2825 break;
2826 }
2827 carets_to_remove.push_back(caret_edit_order[j]);
2828 }
2829 } else {
2830 // Delete one character.
2831 if (caret_mid_grapheme_enabled) {
2832 next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0;
2833 } else {
2834 next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0;
2835 }
2836
2837 // Remove overlapping carets.
2838 if (i > 0) {
2839 int prev_caret_idx = caret_edit_order[i - 1];
2840 if (get_caret_line(prev_caret_idx) == next_line && get_caret_column(prev_caret_idx) == next_column) {
2841 carets_to_remove.push_back(prev_caret_idx);
2842 }
2843 }
2844 }
2845
2846 _remove_text(get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
2847 adjust_carets_after_edit(caret_idx, get_caret_line(caret_idx), get_caret_column(caret_idx), next_line, next_column);
2848 }
2849
2850 // Sort and remove backwards to preserve indexes.
2851 carets_to_remove.sort();
2852 for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
2853 remove_caret(carets_to_remove[i]);
2854 }
2855
2856 // If we are deleting from the end of a line, due to column preservation we could still overlap with another caret.
2857 merge_overlapping_carets();
2858 end_action();
2859 queue_redraw();
2860}
2861
2862void TextEdit::_move_caret_document_start(bool p_select) {
2863 remove_secondary_carets();
2864 if (p_select) {
2865 _pre_shift_selection(0);
2866 } else {
2867 deselect();
2868 }
2869
2870 set_caret_line(0, false);
2871 set_caret_column(0);
2872
2873 if (p_select) {
2874 _post_shift_selection(0);
2875 }
2876}
2877
2878void TextEdit::_move_caret_document_end(bool p_select) {
2879 remove_secondary_carets();
2880 if (p_select) {
2881 _pre_shift_selection(0);
2882 } else {
2883 deselect();
2884 }
2885
2886 set_caret_line(get_last_unhidden_line(), true, false, 9999);
2887 set_caret_column(text[get_caret_line()].length());
2888
2889 if (p_select) {
2890 _post_shift_selection(0);
2891 }
2892}
2893
2894bool TextEdit::_clear_carets_and_selection() {
2895 if (get_caret_count() > 1) {
2896 remove_secondary_carets();
2897 return true;
2898 }
2899
2900 if (has_selection()) {
2901 deselect();
2902 return true;
2903 }
2904
2905 return false;
2906}
2907
2908void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const {
2909 if (p_last_fit_x == -1) {
2910 p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column);
2911 }
2912
2913 // Calculate the new line and wrap index.
2914 p_new_line = p_old_line;
2915 int caret_wrap_index = p_old_wrap_index;
2916 if (p_below) {
2917 if (caret_wrap_index < get_line_wrap_count(p_new_line)) {
2918 caret_wrap_index++;
2919 } else {
2920 p_new_line++;
2921 caret_wrap_index = 0;
2922 }
2923 } else {
2924 if (caret_wrap_index == 0) {
2925 p_new_line--;
2926 caret_wrap_index = get_line_wrap_count(p_new_line);
2927 } else {
2928 caret_wrap_index--;
2929 }
2930 }
2931
2932 // Boundary checks.
2933 if (p_new_line < 0) {
2934 p_new_line = 0;
2935 }
2936 if (p_new_line >= text.size()) {
2937 p_new_line = text.size() - 1;
2938 }
2939
2940 p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index);
2941 if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) {
2942 Vector<String> rows = get_line_wrapped_text(p_new_line);
2943 int row_end_col = 0;
2944 for (int i = 0; i < caret_wrap_index + 1; i++) {
2945 row_end_col += rows[i].length();
2946 }
2947 if (p_new_column >= row_end_col) {
2948 p_new_column -= 1;
2949 }
2950 }
2951}
2952
2953void TextEdit::_update_placeholder() {
2954 if (theme_cache.font.is_null() || theme_cache.font_size <= 0) {
2955 return; // Not in tree?
2956 }
2957
2958 // Placeholder is generally smaller then text documents, and updates less so this should be fast enough for now.
2959 placeholder_data_buf->clear();
2960 placeholder_data_buf->set_width(text.get_width());
2961 placeholder_data_buf->set_break_flags(text.get_brk_flags());
2962 if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
2963 placeholder_data_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
2964 } else {
2965 placeholder_data_buf->set_direction((TextServer::Direction)text_direction);
2966 }
2967 placeholder_data_buf->set_preserve_control(draw_control_chars);
2968 placeholder_data_buf->add_string(placeholder_text, theme_cache.font, theme_cache.font_size, language);
2969
2970 placeholder_bidi_override = structured_text_parser(st_parser, st_args, placeholder_text);
2971 if (placeholder_bidi_override.is_empty()) {
2972 TS->shaped_text_set_bidi_override(placeholder_data_buf->get_rid(), placeholder_bidi_override);
2973 }
2974
2975 if (get_tab_size() > 0) {
2976 Vector<float> tabs;
2977 tabs.push_back(theme_cache.font->get_char_size(' ', theme_cache.font_size).width * get_tab_size());
2978 placeholder_data_buf->tab_align(tabs);
2979 }
2980
2981 // Update height.
2982 const int wrap_amount = placeholder_data_buf->get_line_count() - 1;
2983 placeholder_line_height = theme_cache.font->get_height(theme_cache.font_size);
2984 for (int i = 0; i <= wrap_amount; i++) {
2985 placeholder_line_height = MAX(placeholder_line_height, placeholder_data_buf->get_line_size(i).y);
2986 }
2987
2988 // Update width.
2989 placeholder_max_width = placeholder_data_buf->get_size().x;
2990
2991 // Update wrapped rows.
2992 placeholder_wraped_rows.clear();
2993 for (int i = 0; i <= wrap_amount; i++) {
2994 Vector2i line_range = placeholder_data_buf->get_line_range(i);
2995 placeholder_wraped_rows.push_back(placeholder_text.substr(line_range.x, line_range.y - line_range.x));
2996 }
2997}
2998
2999void TextEdit::_update_theme_item_cache() {
3000 Control::_update_theme_item_cache();
3001
3002 theme_cache.base_scale = get_theme_default_base_scale();
3003 use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0);
3004
3005 if (text.get_line_height() + theme_cache.line_spacing < 1) {
3006 WARN_PRINT("Line height is too small, please increase font_size and/or line_spacing");
3007 }
3008}
3009
3010void TextEdit::_update_caches() {
3011 /* Text properties. */
3012 TextServer::Direction dir;
3013 if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
3014 dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
3015 } else {
3016 dir = (TextServer::Direction)text_direction;
3017 }
3018 text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
3019 text.set_draw_control_chars(draw_control_chars);
3020 text.set_font(theme_cache.font);
3021 text.set_font_size(theme_cache.font_size);
3022 text.invalidate_font();
3023 _update_placeholder();
3024
3025 /* Syntax highlighting. */
3026 if (syntax_highlighter.is_valid()) {
3027 syntax_highlighter->set_text_edit(this);
3028 }
3029}
3030
3031/* General overrides. */
3032Size2 TextEdit::get_minimum_size() const {
3033 Size2 size = theme_cache.style_normal->get_minimum_size();
3034 if (fit_content_height) {
3035 size.y += content_height_cache;
3036 }
3037 return size;
3038}
3039
3040bool TextEdit::is_text_field() const {
3041 return true;
3042}
3043
3044Variant TextEdit::get_drag_data(const Point2 &p_point) {
3045 Variant ret = Control::get_drag_data(p_point);
3046 if (ret != Variant()) {
3047 return ret;
3048 }
3049
3050 if (has_selection() && selection_drag_attempt) {
3051 String t = get_selected_text();
3052 Label *l = memnew(Label);
3053 l->set_text(t);
3054 set_drag_preview(l);
3055 return t;
3056 }
3057
3058 return Variant();
3059}
3060
3061bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
3062 bool drop_override = Control::can_drop_data(p_point, p_data); // In case user wants to drop custom data.
3063 if (drop_override) {
3064 return drop_override;
3065 }
3066
3067 return is_editable() && p_data.get_type() == Variant::STRING;
3068}
3069
3070void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
3071 Control::drop_data(p_point, p_data);
3072
3073 if (p_data.get_type() == Variant::STRING && is_editable()) {
3074 Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
3075 int caret_row_tmp = pos.y;
3076 int caret_column_tmp = pos.x;
3077 if (selection_drag_attempt) {
3078 selection_drag_attempt = false;
3079 if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) {
3080 // Set caret back at selection for undo / redo.
3081 set_caret_line(get_selection_to_line(), false, false);
3082 set_caret_column(get_selection_to_column());
3083
3084 begin_complex_operation();
3085 if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
3086 if (caret_row_tmp > get_selection_to_line()) {
3087 caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line());
3088 } else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) {
3089 caret_column_tmp = caret_column_tmp - (get_selection_to_column() - get_selection_from_column());
3090 }
3091 delete_selection();
3092 } else {
3093 deselect();
3094 }
3095
3096 remove_secondary_carets();
3097 set_caret_line(caret_row_tmp, true, false);
3098 set_caret_column(caret_column_tmp);
3099 insert_text_at_caret(p_data);
3100 end_complex_operation();
3101 }
3102 } else if (is_mouse_over_selection()) {
3103 remove_secondary_carets();
3104 caret_row_tmp = get_selection_from_line();
3105 caret_column_tmp = get_selection_from_column();
3106 set_caret_line(caret_row_tmp, true, false);
3107 set_caret_column(caret_column_tmp);
3108 insert_text_at_caret(p_data);
3109 grab_focus();
3110 } else {
3111 remove_secondary_carets();
3112 deselect();
3113 set_caret_line(caret_row_tmp, true, false);
3114 set_caret_column(caret_column_tmp);
3115 insert_text_at_caret(p_data);
3116 grab_focus();
3117 }
3118
3119 if (caret_row_tmp != get_caret_line() || caret_column_tmp != get_caret_column()) {
3120 select(caret_row_tmp, caret_column_tmp, get_caret_line(), get_caret_column());
3121 }
3122 }
3123}
3124
3125Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
3126 Point2i pos = get_line_column_at_pos(p_pos);
3127 int row = pos.y;
3128
3129 int left_margin = theme_cache.style_normal->get_margin(SIDE_LEFT);
3130 int gutter = left_margin + gutters_width;
3131 if (p_pos.x < gutter) {
3132 for (int i = 0; i < gutters.size(); i++) {
3133 if (!gutters[i].draw) {
3134 continue;
3135 }
3136
3137 if (p_pos.x >= left_margin && p_pos.x < left_margin + gutters[i].width) {
3138 if (gutters[i].clickable || is_line_gutter_clickable(row, i)) {
3139 return CURSOR_POINTING_HAND;
3140 }
3141 }
3142 left_margin += gutters[i].width;
3143 }
3144 return CURSOR_ARROW;
3145 }
3146
3147 int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
3148 if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) {
3149 return CURSOR_ARROW;
3150 }
3151 return get_default_cursor_shape();
3152}
3153
3154String TextEdit::get_tooltip(const Point2 &p_pos) const {
3155 if (!tooltip_callback.is_valid()) {
3156 return Control::get_tooltip(p_pos);
3157 }
3158 Point2i pos = get_line_column_at_pos(p_pos);
3159 int row = pos.y;
3160 int col = pos.x;
3161
3162 String s = text[row];
3163 if (s.length() == 0) {
3164 return Control::get_tooltip(p_pos);
3165 }
3166 int beg, end;
3167 if (select_word(s, col, beg, end)) {
3168 Variant args[1] = { s.substr(beg, end - beg) };
3169 const Variant *argp[] = { &args[0] };
3170 Callable::CallError ce;
3171 Variant ret;
3172 tooltip_callback.callp(argp, 1, ret, ce);
3173 ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, "", "Failed to call custom tooltip.");
3174 return ret;
3175 }
3176
3177 return Control::get_tooltip(p_pos);
3178}
3179
3180void TextEdit::set_tooltip_request_func(const Callable &p_tooltip_callback) {
3181 tooltip_callback = p_tooltip_callback;
3182}
3183
3184/* Text */
3185// Text properties.
3186bool TextEdit::has_ime_text() const {
3187 return !ime_text.is_empty();
3188}
3189
3190void TextEdit::set_editable(const bool p_editable) {
3191 if (editable == p_editable) {
3192 return;
3193 }
3194
3195 editable = p_editable;
3196
3197 queue_redraw();
3198}
3199
3200bool TextEdit::is_editable() const {
3201 return editable;
3202}
3203
3204void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
3205 ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
3206 if (text_direction != p_text_direction) {
3207 text_direction = p_text_direction;
3208 if (text_direction != TEXT_DIRECTION_AUTO && text_direction != TEXT_DIRECTION_INHERITED) {
3209 input_direction = text_direction;
3210 }
3211 TextServer::Direction dir;
3212 if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
3213 dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
3214 } else {
3215 dir = (TextServer::Direction)text_direction;
3216 }
3217 text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
3218 text.invalidate_font();
3219 _update_placeholder();
3220
3221 if (menu_dir) {
3222 menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_INHERITED), text_direction == TEXT_DIRECTION_INHERITED);
3223 menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);
3224 menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);
3225 menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL);
3226 }
3227 queue_redraw();
3228 }
3229}
3230
3231Control::TextDirection TextEdit::get_text_direction() const {
3232 return text_direction;
3233}
3234
3235void TextEdit::set_language(const String &p_language) {
3236 if (language != p_language) {
3237 language = p_language;
3238 TextServer::Direction dir;
3239 if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
3240 dir = is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR;
3241 } else {
3242 dir = (TextServer::Direction)text_direction;
3243 }
3244 text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
3245 text.invalidate_all();
3246 _update_placeholder();
3247 queue_redraw();
3248 }
3249}
3250
3251String TextEdit::get_language() const {
3252 return language;
3253}
3254
3255void TextEdit::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
3256 if (st_parser != p_parser) {
3257 st_parser = p_parser;
3258 for (int i = 0; i < text.size(); i++) {
3259 text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
3260 }
3261 queue_redraw();
3262 }
3263}
3264
3265TextServer::StructuredTextParser TextEdit::get_structured_text_bidi_override() const {
3266 return st_parser;
3267}
3268
3269void TextEdit::set_structured_text_bidi_override_options(Array p_args) {
3270 if (st_args == p_args) {
3271 return;
3272 }
3273
3274 st_args = p_args;
3275 for (int i = 0; i < text.size(); i++) {
3276 text.set(i, text[i], structured_text_parser(st_parser, st_args, text[i]));
3277 }
3278 queue_redraw();
3279}
3280
3281Array TextEdit::get_structured_text_bidi_override_options() const {
3282 return st_args;
3283}
3284
3285void TextEdit::set_tab_size(const int p_size) {
3286 ERR_FAIL_COND_MSG(p_size <= 0, "Tab size must be greater than 0.");
3287 if (p_size == text.get_tab_size()) {
3288 return;
3289 }
3290 text.set_tab_size(p_size);
3291 text.invalidate_all_lines();
3292 _update_placeholder();
3293 queue_redraw();
3294}
3295
3296int TextEdit::get_tab_size() const {
3297 return text.get_tab_size();
3298}
3299
3300// User controls
3301void TextEdit::set_overtype_mode_enabled(const bool p_enabled) {
3302 if (overtype_mode == p_enabled) {
3303 return;
3304 }
3305
3306 overtype_mode = p_enabled;
3307 queue_redraw();
3308}
3309
3310bool TextEdit::is_overtype_mode_enabled() const {
3311 return overtype_mode;
3312}
3313
3314void TextEdit::set_context_menu_enabled(bool p_enabled) {
3315 context_menu_enabled = p_enabled;
3316}
3317
3318bool TextEdit::is_context_menu_enabled() const {
3319 return context_menu_enabled;
3320}
3321
3322void TextEdit::set_shortcut_keys_enabled(bool p_enabled) {
3323 shortcut_keys_enabled = p_enabled;
3324}
3325
3326bool TextEdit::is_shortcut_keys_enabled() const {
3327 return shortcut_keys_enabled;
3328}
3329
3330void TextEdit::set_virtual_keyboard_enabled(bool p_enabled) {
3331 virtual_keyboard_enabled = p_enabled;
3332}
3333
3334bool TextEdit::is_virtual_keyboard_enabled() const {
3335 return virtual_keyboard_enabled;
3336}
3337
3338void TextEdit::set_middle_mouse_paste_enabled(bool p_enabled) {
3339 middle_mouse_paste_enabled = p_enabled;
3340}
3341
3342bool TextEdit::is_middle_mouse_paste_enabled() const {
3343 return middle_mouse_paste_enabled;
3344}
3345
3346// Text manipulation
3347void TextEdit::clear() {
3348 setting_text = true;
3349 _clear();
3350 setting_text = false;
3351 emit_signal(SNAME("text_set"));
3352}
3353
3354void TextEdit::_clear() {
3355 if (editable && undo_enabled) {
3356 remove_secondary_carets();
3357 _move_caret_document_start(false);
3358 begin_complex_operation();
3359
3360 _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0));
3361 insert_text_at_caret("");
3362 text.clear();
3363
3364 end_complex_operation();
3365 return;
3366 }
3367 // Cannot merge with above, as we are not part of the tree on creation.
3368 int old_text_size = text.size();
3369
3370 clear_undo_history();
3371 text.clear();
3372 remove_secondary_carets();
3373 set_caret_line(0, false);
3374 set_caret_column(0);
3375 first_visible_col = 0;
3376 first_visible_line = 0;
3377 first_visible_line_wrap_ofs = 0;
3378 carets.write[0].last_fit_x = 0;
3379 deselect();
3380
3381 emit_signal(SNAME("lines_edited_from"), old_text_size, 0);
3382}
3383
3384void TextEdit::set_text(const String &p_text) {
3385 setting_text = true;
3386 if (!undo_enabled) {
3387 _clear();
3388 insert_text_at_caret(p_text);
3389 }
3390
3391 if (undo_enabled) {
3392 remove_secondary_carets();
3393 set_caret_line(0);
3394 set_caret_column(0);
3395
3396 begin_complex_operation();
3397 deselect();
3398 _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0));
3399 insert_text_at_caret(p_text);
3400 end_complex_operation();
3401 }
3402
3403 set_caret_line(0);
3404 set_caret_column(0);
3405
3406 queue_redraw();
3407 setting_text = false;
3408 emit_signal(SNAME("text_set"));
3409}
3410
3411String TextEdit::get_text() const {
3412 StringBuilder ret_text;
3413 const int text_size = text.size();
3414 for (int i = 0; i < text_size; i++) {
3415 ret_text += text[i];
3416 if (i != text_size - 1) {
3417 ret_text += "\n";
3418 }
3419 }
3420 return ret_text.as_string();
3421}
3422
3423int TextEdit::get_line_count() const {
3424 return text.size();
3425}
3426
3427void TextEdit::set_placeholder(const String &p_text) {
3428 if (placeholder_text == p_text) {
3429 return;
3430 }
3431
3432 placeholder_text = p_text;
3433 _update_placeholder();
3434 queue_redraw();
3435}
3436
3437String TextEdit::get_placeholder() const {
3438 return placeholder_text;
3439}
3440
3441void TextEdit::set_line(int p_line, const String &p_new_text) {
3442 if (p_line < 0 || p_line >= text.size()) {
3443 return;
3444 }
3445 begin_complex_operation();
3446 _remove_text(p_line, 0, p_line, text[p_line].length());
3447 _insert_text(p_line, 0, p_new_text);
3448 for (int i = 0; i < carets.size(); i++) {
3449 if (get_caret_line(i) == p_line && get_caret_column(i) > p_new_text.length()) {
3450 set_caret_column(p_new_text.length(), false, i);
3451 }
3452
3453 if (has_selection(i) && p_line == get_selection_to_line(i) && get_selection_to_column(i) > text[p_line].length()) {
3454 carets.write[i].selection.to_column = text[p_line].length();
3455 }
3456 }
3457 end_complex_operation();
3458}
3459
3460String TextEdit::get_line(int p_line) const {
3461 if (p_line < 0 || p_line >= text.size()) {
3462 return "";
3463 }
3464 return text[p_line];
3465}
3466
3467int TextEdit::get_line_width(int p_line, int p_wrap_index) const {
3468 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
3469 ERR_FAIL_COND_V(p_wrap_index > get_line_wrap_count(p_line), 0);
3470
3471 return text.get_line_width(p_line, p_wrap_index);
3472}
3473
3474int TextEdit::get_line_height() const {
3475 return MAX(text.get_line_height() + theme_cache.line_spacing, 1);
3476}
3477
3478int TextEdit::get_indent_level(int p_line) const {
3479 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
3480
3481 int tab_count = 0;
3482 int whitespace_count = 0;
3483 int line_length = text[p_line].size();
3484 for (int i = 0; i < line_length - 1; i++) {
3485 if (text[p_line][i] == '\t') {
3486 tab_count++;
3487 } else if (text[p_line][i] == ' ') {
3488 whitespace_count++;
3489 } else {
3490 break;
3491 }
3492 }
3493 return tab_count * text.get_tab_size() + whitespace_count;
3494}
3495
3496int TextEdit::get_first_non_whitespace_column(int p_line) const {
3497 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
3498
3499 int col = 0;
3500 while (col < text[p_line].length() && is_whitespace(text[p_line][col])) {
3501 col++;
3502 }
3503 return col;
3504}
3505
3506void TextEdit::swap_lines(int p_from_line, int p_to_line) {
3507 ERR_FAIL_INDEX(p_from_line, text.size());
3508 ERR_FAIL_INDEX(p_to_line, text.size());
3509
3510 String tmp = get_line(p_from_line);
3511 String tmp2 = get_line(p_to_line);
3512 begin_complex_operation();
3513 set_line(p_to_line, tmp);
3514 set_line(p_from_line, tmp2);
3515 end_complex_operation();
3516}
3517
3518void TextEdit::insert_line_at(int p_at, const String &p_text) {
3519 ERR_FAIL_INDEX(p_at, text.size());
3520
3521 _insert_text(p_at, 0, p_text + "\n");
3522
3523 for (int i = 0; i < carets.size(); i++) {
3524 if (get_caret_line(i) >= p_at) {
3525 // Offset caret when located after inserted line.
3526 set_caret_line(get_caret_line(i) + 1, false, true, 0, i);
3527 }
3528 if (has_selection(i)) {
3529 if (get_selection_from_line(i) >= p_at) {
3530 // Offset selection when located after inserted line.
3531 select(get_selection_from_line(i) + 1, get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
3532 } else if (get_selection_to_line(i) >= p_at) {
3533 // Extend selection that includes inserted line.
3534 select(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i) + 1, get_selection_to_column(i), i);
3535 }
3536 }
3537 }
3538
3539 // Need to apply the above adjustments to the undo / redo carets.
3540 current_op.end_carets = carets;
3541 queue_redraw();
3542}
3543
3544void TextEdit::insert_text_at_caret(const String &p_text, int p_caret) {
3545 ERR_FAIL_COND(p_caret > carets.size());
3546
3547 begin_complex_operation();
3548 Vector<int> caret_edit_order = get_caret_index_edit_order();
3549 for (const int &i : caret_edit_order) {
3550 if (p_caret != -1 && p_caret != i) {
3551 continue;
3552 }
3553
3554 delete_selection(i);
3555
3556 int from_line = get_caret_line(i);
3557 int from_col = get_caret_column(i);
3558
3559 int new_column, new_line;
3560 _insert_text(from_line, from_col, p_text, &new_line, &new_column);
3561 _update_scrollbars();
3562
3563 set_caret_line(new_line, false, true, 0, i);
3564 set_caret_column(new_column, i == 0, i);
3565
3566 adjust_carets_after_edit(i, new_line, new_column, from_line, from_col);
3567 }
3568
3569 if (!ime_text.is_empty()) {
3570 for (int i = 0; i < carets.size(); i++) {
3571 String t;
3572 if (get_caret_column(i) >= 0) {
3573 t = text[get_caret_line(i)].substr(0, get_caret_column(i)) + ime_text + text[get_caret_line(i)].substr(get_caret_column(i), text[get_caret_line(i)].length());
3574 } else {
3575 t = ime_text;
3576 }
3577 text.invalidate_cache(get_caret_line(i), get_caret_column(i), true, t, structured_text_parser(st_parser, st_args, t));
3578 }
3579 }
3580
3581 end_complex_operation();
3582 queue_redraw();
3583}
3584
3585void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
3586 ERR_FAIL_INDEX(p_from_line, text.size());
3587 ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1);
3588 ERR_FAIL_INDEX(p_to_line, text.size());
3589 ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1);
3590 ERR_FAIL_COND(p_to_line < p_from_line);
3591 ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column);
3592
3593 _remove_text(p_from_line, p_from_column, p_to_line, p_to_column);
3594}
3595
3596int TextEdit::get_last_unhidden_line() const {
3597 // Returns the last line in the text that is not hidden.
3598 if (!_is_hiding_enabled()) {
3599 return text.size() - 1;
3600 }
3601
3602 int last_line;
3603 for (last_line = text.size() - 1; last_line > 0; last_line--) {
3604 if (!_is_line_hidden(last_line)) {
3605 break;
3606 }
3607 }
3608 return last_line;
3609}
3610
3611int TextEdit::get_next_visible_line_offset_from(int p_line_from, int p_visible_amount) const {
3612 // Returns the number of lines (hidden and unhidden) from p_line_from to (p_line_from + visible_amount of unhidden lines).
3613 ERR_FAIL_INDEX_V(p_line_from, text.size(), ABS(p_visible_amount));
3614
3615 if (!_is_hiding_enabled()) {
3616 return ABS(p_visible_amount);
3617 }
3618
3619 int num_visible = 0;
3620 int num_total = 0;
3621 if (p_visible_amount >= 0) {
3622 for (int i = p_line_from; i < text.size(); i++) {
3623 num_total++;
3624 if (!_is_line_hidden(i)) {
3625 num_visible++;
3626 }
3627 if (num_visible >= p_visible_amount) {
3628 break;
3629 }
3630 }
3631 } else {
3632 p_visible_amount = ABS(p_visible_amount);
3633 for (int i = p_line_from; i >= 0; i--) {
3634 num_total++;
3635 if (!_is_line_hidden(i)) {
3636 num_visible++;
3637 }
3638 if (num_visible >= p_visible_amount) {
3639 break;
3640 }
3641 }
3642 }
3643 return num_total;
3644}
3645
3646Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const {
3647 // Returns the number of lines (hidden and unhidden) from (p_line_from + p_wrap_index_from) row to (p_line_from + visible_amount of unhidden and wrapped rows).
3648 // Wrap index is set to the wrap index of the last line.
3649 int wrap_index = 0;
3650 ERR_FAIL_INDEX_V(p_line_from, text.size(), Point2i(ABS(p_visible_amount), 0));
3651
3652 if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
3653 return Point2i(ABS(p_visible_amount), 0);
3654 }
3655
3656 int num_visible = 0;
3657 int num_total = 0;
3658 if (p_visible_amount == 0) {
3659 num_total = 0;
3660 wrap_index = 0;
3661 } else if (p_visible_amount > 0) {
3662 int i;
3663 num_visible -= p_wrap_index_from;
3664 for (i = p_line_from; i < text.size(); i++) {
3665 num_total++;
3666 if (!_is_line_hidden(i)) {
3667 num_visible++;
3668 num_visible += get_line_wrap_count(i);
3669 }
3670 if (num_visible >= p_visible_amount) {
3671 break;
3672 }
3673 }
3674 wrap_index = get_line_wrap_count(MIN(i, text.size() - 1)) - MAX(0, num_visible - p_visible_amount);
3675
3676 // If we are a hidden line, then we are the last line as we cannot reach "p_visible_amount".
3677 // This means we need to backtrack to get last visible line.
3678 // Currently, line 0 cannot be hidden so this should always be valid.
3679 int line = (p_line_from + num_total) - 1;
3680 if (_is_line_hidden(line)) {
3681 Point2i backtrack = get_next_visible_line_index_offset_from(line, 0, -1);
3682 num_total = num_total - (backtrack.x - 1);
3683 wrap_index = backtrack.y;
3684 }
3685 } else {
3686 p_visible_amount = ABS(p_visible_amount);
3687 int i;
3688 num_visible -= get_line_wrap_count(p_line_from) - p_wrap_index_from;
3689 for (i = p_line_from; i >= 0; i--) {
3690 num_total++;
3691 if (!_is_line_hidden(i)) {
3692 num_visible++;
3693 num_visible += get_line_wrap_count(i);
3694 }
3695 if (num_visible >= p_visible_amount) {
3696 break;
3697 }
3698 }
3699 wrap_index = MAX(0, num_visible - p_visible_amount);
3700 }
3701 wrap_index = MAX(wrap_index, 0);
3702 return Point2i(num_total, wrap_index);
3703}
3704
3705// Overridable actions
3706void TextEdit::handle_unicode_input(const uint32_t p_unicode, int p_caret) {
3707 if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode, p_caret)) {
3708 return;
3709 }
3710 _handle_unicode_input_internal(p_unicode, p_caret);
3711}
3712
3713void TextEdit::backspace(int p_caret) {
3714 if (GDVIRTUAL_CALL(_backspace, p_caret)) {
3715 return;
3716 }
3717 _backspace_internal(p_caret);
3718}
3719
3720void TextEdit::cut(int p_caret) {
3721 if (GDVIRTUAL_CALL(_cut, p_caret)) {
3722 return;
3723 }
3724 _cut_internal(p_caret);
3725}
3726
3727void TextEdit::copy(int p_caret) {
3728 if (GDVIRTUAL_CALL(_copy, p_caret)) {
3729 return;
3730 }
3731 _copy_internal(p_caret);
3732}
3733
3734void TextEdit::paste(int p_caret) {
3735 if (GDVIRTUAL_CALL(_paste, p_caret)) {
3736 return;
3737 }
3738 _paste_internal(p_caret);
3739}
3740
3741void TextEdit::paste_primary_clipboard(int p_caret) {
3742 if (GDVIRTUAL_CALL(_paste_primary_clipboard, p_caret)) {
3743 return;
3744 }
3745 _paste_primary_clipboard_internal(p_caret);
3746}
3747
3748// Context menu.
3749PopupMenu *TextEdit::get_menu() const {
3750 if (!menu) {
3751 const_cast<TextEdit *>(this)->_generate_context_menu();
3752 }
3753 return menu;
3754}
3755
3756bool TextEdit::is_menu_visible() const {
3757 return menu && menu->is_visible();
3758}
3759
3760void TextEdit::menu_option(int p_option) {
3761 switch (p_option) {
3762 case MENU_CUT: {
3763 cut();
3764 } break;
3765 case MENU_COPY: {
3766 copy();
3767 } break;
3768 case MENU_PASTE: {
3769 paste();
3770 } break;
3771 case MENU_CLEAR: {
3772 if (editable) {
3773 clear();
3774 }
3775 } break;
3776 case MENU_SELECT_ALL: {
3777 select_all();
3778 } break;
3779 case MENU_UNDO: {
3780 undo();
3781 } break;
3782 case MENU_REDO: {
3783 redo();
3784 } break;
3785 case MENU_DIR_INHERITED: {
3786 set_text_direction(TEXT_DIRECTION_INHERITED);
3787 } break;
3788 case MENU_DIR_AUTO: {
3789 set_text_direction(TEXT_DIRECTION_AUTO);
3790 } break;
3791 case MENU_DIR_LTR: {
3792 set_text_direction(TEXT_DIRECTION_LTR);
3793 } break;
3794 case MENU_DIR_RTL: {
3795 set_text_direction(TEXT_DIRECTION_RTL);
3796 } break;
3797 case MENU_DISPLAY_UCC: {
3798 set_draw_control_chars(!get_draw_control_chars());
3799 } break;
3800 case MENU_INSERT_LRM: {
3801 if (editable) {
3802 insert_text_at_caret(String::chr(0x200E));
3803 }
3804 } break;
3805 case MENU_INSERT_RLM: {
3806 if (editable) {
3807 insert_text_at_caret(String::chr(0x200F));
3808 }
3809 } break;
3810 case MENU_INSERT_LRE: {
3811 if (editable) {
3812 insert_text_at_caret(String::chr(0x202A));
3813 }
3814 } break;
3815 case MENU_INSERT_RLE: {
3816 if (editable) {
3817 insert_text_at_caret(String::chr(0x202B));
3818 }
3819 } break;
3820 case MENU_INSERT_LRO: {
3821 if (editable) {
3822 insert_text_at_caret(String::chr(0x202D));
3823 }
3824 } break;
3825 case MENU_INSERT_RLO: {
3826 if (editable) {
3827 insert_text_at_caret(String::chr(0x202E));
3828 }
3829 } break;
3830 case MENU_INSERT_PDF: {
3831 if (editable) {
3832 insert_text_at_caret(String::chr(0x202C));
3833 }
3834 } break;
3835 case MENU_INSERT_ALM: {
3836 if (editable) {
3837 insert_text_at_caret(String::chr(0x061C));
3838 }
3839 } break;
3840 case MENU_INSERT_LRI: {
3841 if (editable) {
3842 insert_text_at_caret(String::chr(0x2066));
3843 }
3844 } break;
3845 case MENU_INSERT_RLI: {
3846 if (editable) {
3847 insert_text_at_caret(String::chr(0x2067));
3848 }
3849 } break;
3850 case MENU_INSERT_FSI: {
3851 if (editable) {
3852 insert_text_at_caret(String::chr(0x2068));
3853 }
3854 } break;
3855 case MENU_INSERT_PDI: {
3856 if (editable) {
3857 insert_text_at_caret(String::chr(0x2069));
3858 }
3859 } break;
3860 case MENU_INSERT_ZWJ: {
3861 if (editable) {
3862 insert_text_at_caret(String::chr(0x200D));
3863 }
3864 } break;
3865 case MENU_INSERT_ZWNJ: {
3866 if (editable) {
3867 insert_text_at_caret(String::chr(0x200C));
3868 }
3869 } break;
3870 case MENU_INSERT_WJ: {
3871 if (editable) {
3872 insert_text_at_caret(String::chr(0x2060));
3873 }
3874 } break;
3875 case MENU_INSERT_SHY: {
3876 if (editable) {
3877 insert_text_at_caret(String::chr(0x00AD));
3878 }
3879 }
3880 }
3881}
3882
3883/* Versioning */
3884void TextEdit::start_action(EditAction p_action) {
3885 if (current_action != p_action) {
3886 if (current_action != EditAction::ACTION_NONE) {
3887 in_action = false;
3888 pending_action_end = false;
3889 end_complex_operation();
3890 }
3891
3892 if (p_action != EditAction::ACTION_NONE) {
3893 in_action = true;
3894 begin_complex_operation();
3895 }
3896 } else if (current_action != EditAction::ACTION_NONE) {
3897 pending_action_end = false;
3898 }
3899 current_action = p_action;
3900}
3901
3902void TextEdit::end_action() {
3903 if (current_action != EditAction::ACTION_NONE) {
3904 pending_action_end = true;
3905 }
3906}
3907
3908TextEdit::EditAction TextEdit::get_current_action() const {
3909 return current_action;
3910}
3911
3912void TextEdit::begin_complex_operation() {
3913 _push_current_op();
3914 if (complex_operation_count == 0) {
3915 next_operation_is_complex = true;
3916 current_op.start_carets = carets;
3917 }
3918 complex_operation_count++;
3919}
3920
3921void TextEdit::end_complex_operation() {
3922 _push_current_op();
3923
3924 complex_operation_count = MAX(complex_operation_count - 1, 0);
3925 if (complex_operation_count > 0) {
3926 return;
3927 }
3928 ERR_FAIL_COND(undo_stack.size() == 0);
3929
3930 undo_stack.back()->get().end_carets = carets;
3931 if (undo_stack.back()->get().chain_forward) {
3932 undo_stack.back()->get().chain_forward = false;
3933 return;
3934 }
3935
3936 undo_stack.back()->get().chain_backward = true;
3937}
3938
3939bool TextEdit::has_undo() const {
3940 if (undo_stack_pos == nullptr) {
3941 int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1;
3942 return undo_stack.size() + pending > 0;
3943 }
3944 return undo_stack_pos != undo_stack.front();
3945}
3946
3947bool TextEdit::has_redo() const {
3948 return undo_stack_pos != nullptr;
3949}
3950
3951void TextEdit::undo() {
3952 if (!editable) {
3953 return;
3954 }
3955
3956 if (in_action) {
3957 pending_action_end = true;
3958 }
3959 _push_current_op();
3960
3961 if (undo_stack_pos == nullptr) {
3962 if (!undo_stack.size()) {
3963 return; // Nothing to undo.
3964 }
3965
3966 undo_stack_pos = undo_stack.back();
3967
3968 } else if (undo_stack_pos == undo_stack.front()) {
3969 return; // At the bottom of the undo stack.
3970 } else {
3971 undo_stack_pos = undo_stack_pos->prev();
3972 }
3973
3974 deselect();
3975
3976 TextOperation op = undo_stack_pos->get();
3977 _do_text_op(op, true);
3978
3979 current_op.version = op.prev_version;
3980 if (undo_stack_pos->get().chain_backward) {
3981 while (true) {
3982 ERR_BREAK(!undo_stack_pos->prev());
3983 undo_stack_pos = undo_stack_pos->prev();
3984 op = undo_stack_pos->get();
3985 _do_text_op(op, true);
3986 current_op.version = op.prev_version;
3987 if (undo_stack_pos->get().chain_forward) {
3988 break;
3989 }
3990 }
3991 }
3992
3993 _update_scrollbars();
3994 bool dirty_carets = carets.size() != undo_stack_pos->get().start_carets.size();
3995 if (!dirty_carets) {
3996 for (int i = 0; i < carets.size(); i++) {
3997 if (carets[i].line != undo_stack_pos->get().start_carets[i].line || carets[i].column != undo_stack_pos->get().start_carets[i].column) {
3998 dirty_carets = true;
3999 break;
4000 }
4001 }
4002 }
4003
4004 carets = undo_stack_pos->get().start_carets;
4005
4006 if (dirty_carets && !caret_pos_dirty) {
4007 if (is_inside_tree()) {
4008 MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
4009 }
4010 caret_pos_dirty = true;
4011 }
4012 adjust_viewport_to_caret();
4013}
4014
4015void TextEdit::redo() {
4016 if (!editable) {
4017 return;
4018 }
4019
4020 if (in_action) {
4021 pending_action_end = true;
4022 }
4023 _push_current_op();
4024
4025 if (undo_stack_pos == nullptr) {
4026 return; // Nothing to do.
4027 }
4028
4029 deselect();
4030
4031 TextOperation op = undo_stack_pos->get();
4032 _do_text_op(op, false);
4033 current_op.version = op.version;
4034 if (undo_stack_pos->get().chain_forward) {
4035 while (true) {
4036 ERR_BREAK(!undo_stack_pos->next());
4037 undo_stack_pos = undo_stack_pos->next();
4038 op = undo_stack_pos->get();
4039 _do_text_op(op, false);
4040 current_op.version = op.version;
4041 if (undo_stack_pos->get().chain_backward) {
4042 break;
4043 }
4044 }
4045 }
4046
4047 _update_scrollbars();
4048 bool dirty_carets = carets.size() != undo_stack_pos->get().end_carets.size();
4049 if (!dirty_carets) {
4050 for (int i = 0; i < carets.size(); i++) {
4051 if (carets[i].line != undo_stack_pos->get().end_carets[i].line || carets[i].column != undo_stack_pos->get().end_carets[i].column) {
4052 dirty_carets = true;
4053 break;
4054 }
4055 }
4056 }
4057
4058 carets = undo_stack_pos->get().end_carets;
4059 undo_stack_pos = undo_stack_pos->next();
4060
4061 if (dirty_carets && !caret_pos_dirty) {
4062 if (is_inside_tree()) {
4063 MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
4064 }
4065 caret_pos_dirty = true;
4066 }
4067 adjust_viewport_to_caret();
4068}
4069
4070void TextEdit::clear_undo_history() {
4071 saved_version = 0;
4072 current_op.type = TextOperation::TYPE_NONE;
4073 undo_stack_pos = nullptr;
4074 undo_stack.clear();
4075}
4076
4077bool TextEdit::is_insert_text_operation() const {
4078 return (current_op.type == TextOperation::TYPE_INSERT || current_action == EditAction::ACTION_TYPING);
4079}
4080
4081void TextEdit::tag_saved_version() {
4082 saved_version = get_version();
4083}
4084
4085uint32_t TextEdit::get_version() const {
4086 return current_op.version;
4087}
4088
4089uint32_t TextEdit::get_saved_version() const {
4090 return saved_version;
4091}
4092
4093/* Search */
4094void TextEdit::set_search_text(const String &p_search_text) {
4095 search_text = p_search_text;
4096}
4097
4098void TextEdit::set_search_flags(uint32_t p_flags) {
4099 search_flags = p_flags;
4100}
4101
4102Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column) const {
4103 if (p_key.length() == 0) {
4104 return Point2(-1, -1);
4105 }
4106 ERR_FAIL_INDEX_V(p_from_line, text.size(), Point2i(-1, -1));
4107 ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, Point2i(-1, -1));
4108
4109 // Search through the whole document, but start by current line.
4110
4111 int line = p_from_line;
4112 int pos = -1;
4113
4114 for (int i = 0; i < text.size() + 1; i++) {
4115 if (line < 0) {
4116 line = text.size() - 1;
4117 }
4118 if (line == text.size()) {
4119 line = 0;
4120 }
4121
4122 String text_line = text[line];
4123 int from_column = 0;
4124 if (line == p_from_line) {
4125 if (i == text.size()) {
4126 // Wrapped.
4127
4128 if (p_search_flags & SEARCH_BACKWARDS) {
4129 from_column = text_line.length();
4130 } else {
4131 from_column = 0;
4132 }
4133
4134 } else {
4135 from_column = p_from_column;
4136 }
4137
4138 } else {
4139 if (p_search_flags & SEARCH_BACKWARDS) {
4140 from_column = text_line.length() - 1;
4141 } else {
4142 from_column = 0;
4143 }
4144 }
4145
4146 pos = -1;
4147
4148 int pos_from = (p_search_flags & SEARCH_BACKWARDS) ? text_line.length() : 0;
4149 int last_pos = -1;
4150
4151 while (true) {
4152 if (p_search_flags & SEARCH_BACKWARDS) {
4153 while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.rfind(p_key, pos_from) : text_line.rfindn(p_key, pos_from)) != -1) {
4154 if (last_pos <= from_column) {
4155 pos = last_pos;
4156 break;
4157 }
4158 pos_from = last_pos - p_key.length();
4159 if (pos_from < 0) {
4160 break;
4161 }
4162 }
4163 } else {
4164 while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.find(p_key, pos_from) : text_line.findn(p_key, pos_from)) != -1) {
4165 if (last_pos >= from_column) {
4166 pos = last_pos;
4167 break;
4168 }
4169 pos_from = last_pos + p_key.length();
4170 }
4171 }
4172
4173 bool is_match = true;
4174
4175 if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) {
4176 // Validate for whole words.
4177 if (pos > 0 && !is_symbol(text_line[pos - 1])) {
4178 is_match = false;
4179 } else if (pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) {
4180 is_match = false;
4181 }
4182 }
4183
4184 if (pos_from == -1) {
4185 pos = -1;
4186 }
4187
4188 if (is_match || last_pos == -1 || pos == -1) {
4189 break;
4190 }
4191
4192 pos_from = (p_search_flags & SEARCH_BACKWARDS) ? pos - 1 : pos + 1;
4193 pos = -1;
4194 }
4195
4196 if (pos != -1) {
4197 break;
4198 }
4199
4200 if (p_search_flags & SEARCH_BACKWARDS) {
4201 line--;
4202 } else {
4203 line++;
4204 }
4205 }
4206 return (pos == -1) ? Point2i(-1, -1) : Point2i(pos, line);
4207}
4208
4209/* Mouse */
4210Point2 TextEdit::get_local_mouse_pos() const {
4211 Point2 mp = get_local_mouse_position();
4212 if (is_layout_rtl()) {
4213 mp.x = get_size().width - mp.x;
4214 }
4215 return mp;
4216}
4217
4218String TextEdit::get_word_at_pos(const Vector2 &p_pos) const {
4219 Point2i pos = get_line_column_at_pos(p_pos);
4220 int row = pos.y;
4221 int col = pos.x;
4222
4223 String s = text[row];
4224 if (s.length() == 0) {
4225 return "";
4226 }
4227 int beg, end;
4228 if (select_word(s, col, beg, end)) {
4229 bool inside_quotes = false;
4230 char32_t selected_quote = '\0';
4231 int qbegin = 0, qend = 0;
4232 for (int i = 0; i < s.length(); i++) {
4233 if (s[i] == '"' || s[i] == '\'') {
4234 if (i == 0 || s[i - 1] != '\\') {
4235 if (inside_quotes && selected_quote == s[i]) {
4236 qend = i;
4237 inside_quotes = false;
4238 selected_quote = '\0';
4239 if (col >= qbegin && col <= qend) {
4240 return s.substr(qbegin, qend - qbegin);
4241 }
4242 } else if (!inside_quotes) {
4243 qbegin = i + 1;
4244 inside_quotes = true;
4245 selected_quote = s[i];
4246 }
4247 }
4248 }
4249 }
4250
4251 return s.substr(beg, end - beg);
4252 }
4253
4254 return String();
4255}
4256
4257Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_of_bounds) const {
4258 float rows = p_pos.y;
4259 rows -= theme_cache.style_normal->get_margin(SIDE_TOP);
4260 rows /= get_line_height();
4261 rows += _get_v_scroll_offset();
4262 int first_vis_line = get_first_visible_line();
4263 int row = first_vis_line + Math::floor(rows);
4264 int wrap_index = 0;
4265
4266 if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
4267 Point2i f_ofs = get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows)));
4268 wrap_index = f_ofs.y;
4269
4270 if (rows < 0) {
4271 row = first_vis_line - (f_ofs.x - 1);
4272 } else {
4273 row = first_vis_line + (f_ofs.x - 1);
4274 }
4275 }
4276
4277 if (row < 0) {
4278 row = 0;
4279 }
4280
4281 if (row >= text.size()) {
4282 row = text.size() - 1;
4283 }
4284
4285 int visible_lines = get_visible_line_count_in_range(first_vis_line, row);
4286 if (rows > visible_lines) {
4287 if (!p_allow_out_of_bounds) {
4288 return Point2i(-1, -1);
4289 }
4290 return Point2i(text[row].length(), row);
4291 }
4292
4293 int col = 0;
4294 int colx = p_pos.x - (theme_cache.style_normal->get_margin(SIDE_LEFT) + gutters_width + gutter_padding);
4295 colx += first_visible_col;
4296 col = _get_char_pos_for_line(colx, row, wrap_index);
4297 if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && wrap_index < get_line_wrap_count(row)) {
4298 // Move back one if we are at the end of the row.
4299 Vector<String> rows2 = get_line_wrapped_text(row);
4300 int row_end_col = 0;
4301 for (int i = 0; i < wrap_index + 1; i++) {
4302 row_end_col += rows2[i].length();
4303 }
4304 if (col >= row_end_col) {
4305 col -= 1;
4306 }
4307 }
4308
4309 RID text_rid = text.get_line_data(row)->get_line_rid(wrap_index);
4310 if (is_layout_rtl()) {
4311 colx = TS->shaped_text_get_size(text_rid).x - colx;
4312 }
4313 col = TS->shaped_text_hit_test_position(text_rid, colx);
4314 if (!caret_mid_grapheme_enabled) {
4315 col = TS->shaped_text_closest_character_pos(text_rid, col);
4316 }
4317
4318 return Point2i(col, row);
4319}
4320
4321Point2i TextEdit::get_pos_at_line_column(int p_line, int p_column) const {
4322 Rect2i rect = get_rect_at_line_column(p_line, p_column);
4323 return rect.position.x == -1 ? rect.position : rect.position + Vector2i(0, get_line_height());
4324}
4325
4326Rect2i TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
4327 ERR_FAIL_INDEX_V(p_line, text.size(), Rect2i(-1, -1, 0, 0));
4328 ERR_FAIL_COND_V(p_column < 0, Rect2i(-1, -1, 0, 0));
4329 ERR_FAIL_COND_V(p_column > text[p_line].length(), Rect2i(-1, -1, 0, 0));
4330
4331 if (line_drawing_cache.size() == 0 || !line_drawing_cache.has(p_line)) {
4332 // Line is not in the cache, which means it's outside of the viewing area.
4333 return Rect2i(-1, -1, 0, 0);
4334 }
4335 LineDrawingCache cache_entry = line_drawing_cache[p_line];
4336
4337 int wrap_index = get_line_wrap_index_at_column(p_line, p_column);
4338 if (wrap_index >= cache_entry.first_visible_chars.size()) {
4339 // Line seems to be wrapped beyond the viewable area.
4340 return Rect2i(-1, -1, 0, 0);
4341 }
4342
4343 int first_visible_char = cache_entry.first_visible_chars[wrap_index];
4344 int last_visible_char = cache_entry.last_visible_chars[wrap_index];
4345 if (p_column < first_visible_char || p_column > last_visible_char) {
4346 // Character is outside of the viewing area, no point calculating its position.
4347 return Rect2i(-1, -1, 0, 0);
4348 }
4349
4350 Point2i pos, size;
4351 pos.y = cache_entry.y_offset + get_line_height() * wrap_index;
4352 pos.x = get_total_gutter_width() + theme_cache.style_normal->get_margin(SIDE_LEFT) - get_h_scroll();
4353
4354 RID text_rid = text.get_line_data(p_line)->get_line_rid(wrap_index);
4355 Vector2 col_bounds = TS->shaped_text_get_grapheme_bounds(text_rid, p_column);
4356 pos.x += col_bounds.x;
4357 size.x = col_bounds.y - col_bounds.x;
4358
4359 size.y = get_line_height();
4360
4361 return Rect2i(pos, size);
4362}
4363
4364int TextEdit::get_minimap_line_at_pos(const Point2i &p_pos) const {
4365 float rows = p_pos.y;
4366 rows -= theme_cache.style_normal->get_margin(SIDE_TOP);
4367 rows /= (minimap_char_size.y + minimap_line_spacing);
4368 rows += _get_v_scroll_offset();
4369
4370 // Calculate visible lines.
4371 int minimap_visible_lines = get_minimap_visible_lines();
4372 int visible_rows = get_visible_line_count() + 1;
4373 int first_vis_line = get_first_visible_line() - 1;
4374 int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
4375 draw_amount += get_line_wrap_count(first_vis_line + 1);
4376 int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
4377
4378 // Calculate viewport size and y offset.
4379 int viewport_height = (draw_amount - 1) * minimap_line_height;
4380 int control_height = _get_control_height() - viewport_height;
4381 int viewport_offset_y = round(get_scroll_pos_for_line(first_vis_line + 1) * control_height) / ((v_scroll->get_max() <= minimap_visible_lines) ? (minimap_visible_lines - draw_amount) : (v_scroll->get_max() - draw_amount));
4382
4383 // Calculate the first line.
4384 int num_lines_before = round((viewport_offset_y) / minimap_line_height);
4385 int minimap_line = (v_scroll->get_max() <= minimap_visible_lines) ? -1 : first_vis_line;
4386 if (first_vis_line > 0 && minimap_line >= 0) {
4387 minimap_line -= get_next_visible_line_index_offset_from(first_vis_line, 0, -num_lines_before).x;
4388 minimap_line -= (minimap_line > 0 && smooth_scroll_enabled ? 1 : 0);
4389 }
4390
4391 if (minimap_line < 0) {
4392 minimap_line = 0;
4393 }
4394
4395 int row = minimap_line + Math::floor(rows);
4396 if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE || _is_hiding_enabled()) {
4397 int f_ofs = get_next_visible_line_index_offset_from(minimap_line, first_visible_line_wrap_ofs, rows + (1 * SIGN(rows))).x - 1;
4398 if (rows < 0) {
4399 row = minimap_line - f_ofs;
4400 } else {
4401 row = minimap_line + f_ofs;
4402 }
4403 }
4404
4405 if (row < 0) {
4406 row = 0;
4407 }
4408
4409 if (row >= text.size()) {
4410 row = text.size() - 1;
4411 }
4412
4413 return row;
4414}
4415
4416bool TextEdit::is_dragging_cursor() const {
4417 return dragging_selection || dragging_minimap;
4418}
4419
4420bool TextEdit::is_mouse_over_selection(bool p_edges, int p_caret) const {
4421 for (int i = 0; i < carets.size(); i++) {
4422 if (p_caret != -1 && p_caret != i) {
4423 continue;
4424 }
4425
4426 if (!has_selection(i)) {
4427 continue;
4428 }
4429
4430 Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
4431 int row = pos.y;
4432 int col = pos.x;
4433 if (p_edges) {
4434 if ((row == get_selection_from_line(i) && col == get_selection_from_column(i)) || (row == get_selection_to_line(i) && col == get_selection_to_column(i))) {
4435 return true;
4436 }
4437 }
4438
4439 if (row >= get_selection_from_line(i) && row <= get_selection_to_line(i) && (row > get_selection_from_line(i) || col > get_selection_from_column(i)) && (row < get_selection_to_line(i) || col < get_selection_to_column(i))) {
4440 return true;
4441 }
4442 }
4443
4444 return false;
4445}
4446
4447/* Caret */
4448void TextEdit::set_caret_type(CaretType p_type) {
4449 if (caret_type == p_type) {
4450 return;
4451 }
4452
4453 caret_type = p_type;
4454 queue_redraw();
4455}
4456
4457TextEdit::CaretType TextEdit::get_caret_type() const {
4458 return caret_type;
4459}
4460
4461void TextEdit::set_caret_blink_enabled(const bool p_enabled) {
4462 if (caret_blink_enabled == p_enabled) {
4463 return;
4464 }
4465
4466 caret_blink_enabled = p_enabled;
4467
4468 if (has_focus()) {
4469 if (p_enabled) {
4470 caret_blink_timer->start();
4471 } else {
4472 caret_blink_timer->stop();
4473 }
4474 }
4475 draw_caret = true;
4476}
4477
4478bool TextEdit::is_caret_blink_enabled() const {
4479 return caret_blink_enabled;
4480}
4481
4482float TextEdit::get_caret_blink_interval() const {
4483 return caret_blink_timer->get_wait_time();
4484}
4485
4486void TextEdit::set_caret_blink_interval(const float p_interval) {
4487 ERR_FAIL_COND(p_interval <= 0);
4488 caret_blink_timer->set_wait_time(p_interval);
4489}
4490
4491void TextEdit::set_draw_caret_when_editable_disabled(bool p_enable) {
4492 if (draw_caret_when_editable_disabled == p_enable) {
4493 return;
4494 }
4495 draw_caret_when_editable_disabled = p_enable;
4496 queue_redraw();
4497}
4498
4499bool TextEdit::is_drawing_caret_when_editable_disabled() const {
4500 return draw_caret_when_editable_disabled;
4501}
4502
4503void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) {
4504 move_caret_on_right_click = p_enabled;
4505}
4506
4507bool TextEdit::is_move_caret_on_right_click_enabled() const {
4508 return move_caret_on_right_click;
4509}
4510
4511void TextEdit::set_caret_mid_grapheme_enabled(const bool p_enabled) {
4512 caret_mid_grapheme_enabled = p_enabled;
4513}
4514
4515bool TextEdit::is_caret_mid_grapheme_enabled() const {
4516 return caret_mid_grapheme_enabled;
4517}
4518
4519void TextEdit::set_multiple_carets_enabled(bool p_enabled) {
4520 multi_carets_enabled = p_enabled;
4521 if (!multi_carets_enabled) {
4522 remove_secondary_carets();
4523 }
4524}
4525
4526bool TextEdit::is_multiple_carets_enabled() const {
4527 return multi_carets_enabled;
4528}
4529
4530int TextEdit::add_caret(int p_line, int p_col) {
4531 if (!multi_carets_enabled) {
4532 return -1;
4533 }
4534
4535 p_line = CLAMP(p_line, 0, text.size() - 1);
4536 p_col = CLAMP(p_col, 0, get_line(p_line).length());
4537
4538 for (int i = 0; i < carets.size(); i++) {
4539 if (get_caret_line(i) == p_line && get_caret_column(i) == p_col) {
4540 return -1;
4541 }
4542
4543 if (has_selection(i)) {
4544 if (p_line >= get_selection_from_line(i) && p_line <= get_selection_to_line(i) && (p_line > get_selection_from_line(i) || p_col >= get_selection_from_column(i)) && (p_line < get_selection_to_line(i) || p_col <= get_selection_to_column(i))) {
4545 return -1;
4546 }
4547 }
4548 }
4549
4550 carets.push_back(Caret());
4551 set_caret_line(p_line, false, false, 0, carets.size() - 1);
4552 set_caret_column(p_col, false, carets.size() - 1);
4553 caret_index_edit_dirty = true;
4554 return carets.size() - 1;
4555}
4556
4557void TextEdit::remove_caret(int p_caret) {
4558 ERR_FAIL_COND_MSG(carets.size() <= 1, "The main caret should not be removed.");
4559 ERR_FAIL_INDEX(p_caret, carets.size());
4560 carets.remove_at(p_caret);
4561 caret_index_edit_dirty = true;
4562}
4563
4564void TextEdit::remove_secondary_carets() {
4565 carets.resize(1);
4566 caret_index_edit_dirty = true;
4567 queue_redraw();
4568}
4569
4570void TextEdit::merge_overlapping_carets() {
4571 Vector<int> caret_edit_order = get_caret_index_edit_order();
4572 for (int i = 0; i < caret_edit_order.size() - 1; i++) {
4573 int first_caret = caret_edit_order[i];
4574 int second_caret = caret_edit_order[i + 1];
4575
4576 // Both have selection.
4577 if (has_selection(first_caret) && has_selection(second_caret)) {
4578 bool should_merge = false;
4579 if (get_selection_from_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_from_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_from_line(first_caret) > get_selection_from_line(second_caret) || get_selection_from_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_from_line(first_caret) < get_selection_to_line(second_caret) || get_selection_from_column(first_caret) <= get_selection_to_column(second_caret))) {
4580 should_merge = true;
4581 }
4582
4583 if (get_selection_to_line(first_caret) >= get_selection_from_line(second_caret) && get_selection_to_line(first_caret) <= get_selection_to_line(second_caret) && (get_selection_to_line(first_caret) > get_selection_from_line(second_caret) || get_selection_to_column(first_caret) >= get_selection_from_column(second_caret)) && (get_selection_to_line(first_caret) < get_selection_to_line(second_caret) || get_selection_to_column(first_caret) <= get_selection_to_column(second_caret))) {
4584 should_merge = true;
4585 }
4586
4587 if (!should_merge) {
4588 continue;
4589 }
4590
4591 // Save the newest one for Click + Drag.
4592 int caret_to_save = first_caret;
4593 int caret_to_remove = second_caret;
4594 if (first_caret < second_caret) {
4595 caret_to_save = second_caret;
4596 caret_to_remove = first_caret;
4597 }
4598
4599 int from_line = MIN(get_selection_from_line(caret_to_save), get_selection_from_line(caret_to_remove));
4600 int to_line = MAX(get_selection_to_line(caret_to_save), get_selection_to_line(caret_to_remove));
4601 int from_col = get_selection_from_column(caret_to_save);
4602 int to_col = get_selection_to_column(caret_to_save);
4603 int selection_line = get_selection_line(caret_to_save);
4604 int selection_col = get_selection_column(caret_to_save);
4605
4606 bool at_from = (get_caret_line(caret_to_save) == get_selection_from_line(caret_to_save) && get_caret_column(caret_to_save) == get_selection_from_column(caret_to_save));
4607
4608 if (at_from) {
4609 if (get_selection_line(caret_to_remove) > get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) >= get_selection_column(caret_to_save))) {
4610 selection_line = get_selection_line(caret_to_remove);
4611 selection_col = get_selection_column(caret_to_remove);
4612 }
4613 } else if (get_selection_line(caret_to_remove) < get_selection_line(caret_to_save) || (get_selection_line(caret_to_remove) == get_selection_line(caret_to_save) && get_selection_column(caret_to_remove) <= get_selection_column(caret_to_save))) {
4614 selection_line = get_selection_line(caret_to_remove);
4615 selection_col = get_selection_column(caret_to_remove);
4616 }
4617
4618 if (get_selection_from_line(caret_to_remove) < get_selection_from_line(caret_to_save) || (get_selection_from_line(caret_to_remove) == get_selection_from_line(caret_to_save) && get_selection_from_column(caret_to_remove) <= get_selection_from_column(caret_to_save))) {
4619 from_col = get_selection_from_column(caret_to_remove);
4620 } else {
4621 to_col = get_selection_to_column(caret_to_remove);
4622 }
4623
4624 select(from_line, from_col, to_line, to_col, caret_to_save);
4625 set_selection_mode(selecting_mode, selection_line, selection_col, caret_to_save);
4626 set_caret_line((at_from ? from_line : to_line), caret_to_save == 0, true, 0, caret_to_save);
4627 set_caret_column((at_from ? from_col : to_col), caret_to_save == 0, caret_to_save);
4628 remove_caret(caret_to_remove);
4629 i--;
4630 caret_edit_order = get_caret_index_edit_order();
4631 continue;
4632 }
4633
4634 // Only first has selection.
4635 if (has_selection(first_caret)) {
4636 if (get_caret_line(second_caret) >= get_selection_from_line(first_caret) && get_caret_line(second_caret) <= get_selection_to_line(first_caret) && (get_caret_line(second_caret) > get_selection_from_line(first_caret) || get_caret_column(second_caret) >= get_selection_from_column(first_caret)) && (get_caret_line(second_caret) < get_selection_to_line(first_caret) || get_caret_column(second_caret) <= get_selection_to_column(first_caret))) {
4637 remove_caret(second_caret);
4638 caret_edit_order = get_caret_index_edit_order();
4639 i--;
4640 }
4641 continue;
4642 }
4643
4644 // Only second has selection.
4645 if (has_selection(second_caret)) {
4646 if (get_caret_line(first_caret) >= get_selection_from_line(second_caret) && get_caret_line(first_caret) <= get_selection_to_line(second_caret) && (get_caret_line(first_caret) > get_selection_from_line(second_caret) || get_caret_column(first_caret) >= get_selection_from_column(second_caret)) && (get_caret_line(first_caret) < get_selection_to_line(second_caret) || get_caret_column(first_caret) <= get_selection_to_column(second_caret))) {
4647 remove_caret(first_caret);
4648 caret_edit_order = get_caret_index_edit_order();
4649 i--;
4650 }
4651 continue;
4652 }
4653
4654 // Both have no selection.
4655 if (get_caret_line(first_caret) == get_caret_line(second_caret) && get_caret_column(first_caret) == get_caret_column(second_caret)) {
4656 // Save the newest one for Click + Drag.
4657 if (first_caret < second_caret) {
4658 remove_caret(first_caret);
4659 } else {
4660 remove_caret(second_caret);
4661 }
4662 i--;
4663 caret_edit_order = get_caret_index_edit_order();
4664 continue;
4665 }
4666 }
4667}
4668
4669int TextEdit::get_caret_count() const {
4670 return carets.size();
4671}
4672
4673void TextEdit::add_caret_at_carets(bool p_below) {
4674 Vector<int> caret_edit_order = get_caret_index_edit_order();
4675 for (const int &caret_index : caret_edit_order) {
4676 const int caret_line = get_caret_line(caret_index);
4677 const int caret_column = get_caret_column(caret_index);
4678
4679 // The last fit x will be cleared if the caret has a selection,
4680 // but if it does not have a selection the last fit x will be
4681 // transferred to the new caret.
4682 int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x;
4683 if (has_selection(caret_index)) {
4684 // If the selection goes over multiple lines, deselect it.
4685 if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) {
4686 deselect(caret_index);
4687 } else {
4688 caret_from_column = get_selection_from_column(caret_index);
4689 caret_to_column = get_selection_to_column(caret_index);
4690 caret_last_fit_x = -1;
4691 carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column);
4692 }
4693 }
4694
4695 // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys.
4696 int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0;
4697 _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x);
4698
4699 // If the caret does have a selection calculate the new from and to columns.
4700 if (caret_from_column != caret_to_column) {
4701 // We only need to calculate the selection columns if the column of the caret changed.
4702 if (caret_column != new_caret_column) {
4703 int _; // Unused placeholder for p_new_line.
4704 _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column);
4705 _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column);
4706 } else {
4707 new_caret_from_column = caret_from_column;
4708 new_caret_to_column = caret_to_column;
4709 }
4710 }
4711
4712 // Add the new caret.
4713 const int new_caret_index = add_caret(new_caret_line, new_caret_column);
4714
4715 if (new_caret_index == -1) {
4716 continue;
4717 }
4718 // Also add the selection if there should be one.
4719 if (new_caret_from_column != new_caret_to_column) {
4720 select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index);
4721 // Necessary to properly modify the selection after adding the new caret.
4722 carets.write[new_caret_index].selection.selecting_line = new_caret_line;
4723 carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column;
4724 continue;
4725 }
4726
4727 // Copy the last fit x over.
4728 carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x;
4729 }
4730
4731 merge_overlapping_carets();
4732 queue_redraw();
4733}
4734
4735Vector<int> TextEdit::get_caret_index_edit_order() {
4736 if (!caret_index_edit_dirty) {
4737 return caret_index_edit_order;
4738 }
4739
4740 caret_index_edit_order.clear();
4741 caret_index_edit_order.push_back(0);
4742 for (int i = 1; i < carets.size(); i++) {
4743 int j = 0;
4744
4745 int line = has_selection(i) ? get_selection_to_line(i) : carets[i].line;
4746 int col = has_selection(i) ? get_selection_to_column(i) : carets[i].column;
4747
4748 for (; j < caret_index_edit_order.size(); j++) {
4749 int idx = caret_index_edit_order[j];
4750 int other_line = has_selection(idx) ? get_selection_to_line(idx) : carets[idx].line;
4751 int other_col = has_selection(idx) ? get_selection_to_column(idx) : carets[idx].column;
4752 if (line > other_line || (line == other_line && col > other_col)) {
4753 break;
4754 }
4755 }
4756 caret_index_edit_order.insert(j, i);
4757 }
4758 caret_index_edit_dirty = false;
4759 return caret_index_edit_order;
4760}
4761
4762void TextEdit::adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col) {
4763 int edit_height = p_from_line - p_to_line;
4764 int edit_size = ((edit_height == 0) ? p_from_col : 0) - p_to_col;
4765
4766 Vector<int> caret_edit_order = get_caret_index_edit_order();
4767 for (int j = 0; j < caret_edit_order.size(); j++) {
4768 if (caret_edit_order[j] == p_caret) {
4769 return;
4770 }
4771
4772 // Adjust caret.
4773 // set_caret_line could adjust the column, so save here.
4774 int cc = get_caret_column(caret_edit_order[j]);
4775 if (edit_height != 0) {
4776 set_caret_line(get_caret_line(caret_edit_order[j]) + edit_height, false, true, 0, caret_edit_order[j]);
4777 }
4778 if (get_caret_line(p_caret) == get_caret_line(caret_edit_order[j])) {
4779 set_caret_column(cc + edit_size, false, caret_edit_order[j]);
4780 }
4781
4782 // Adjust selection.
4783 if (!has_selection(caret_edit_order[j])) {
4784 continue;
4785 }
4786 if (edit_height != 0) {
4787 carets.write[caret_edit_order[j]].selection.from_line += edit_height;
4788 carets.write[caret_edit_order[j]].selection.to_line += edit_height;
4789 }
4790 if (get_caret_line(p_caret) == get_selection_from_line(caret_edit_order[j])) {
4791 carets.write[caret_edit_order[j]].selection.from_column += edit_size;
4792 }
4793 }
4794}
4795
4796bool TextEdit::is_caret_visible(int p_caret) const {
4797 ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
4798 return carets[p_caret].visible;
4799}
4800
4801Point2 TextEdit::get_caret_draw_pos(int p_caret) const {
4802 ERR_FAIL_INDEX_V(p_caret, carets.size(), Point2(0, 0));
4803 return carets[p_caret].draw_pos;
4804}
4805
4806void TextEdit::set_caret_line(int p_line, bool p_adjust_viewport, bool p_can_be_hidden, int p_wrap_index, int p_caret) {
4807 ERR_FAIL_INDEX(p_caret, carets.size());
4808 if (setting_caret_line) {
4809 return;
4810 }
4811
4812 setting_caret_line = true;
4813 if (p_line < 0) {
4814 p_line = 0;
4815 }
4816
4817 if (p_line >= text.size()) {
4818 p_line = text.size() - 1;
4819 }
4820
4821 if (!p_can_be_hidden) {
4822 if (_is_line_hidden(CLAMP(p_line, 0, text.size() - 1))) {
4823 int move_down = get_next_visible_line_offset_from(p_line, 1) - 1;
4824 if (p_line + move_down <= text.size() - 1 && !_is_line_hidden(p_line + move_down)) {
4825 p_line += move_down;
4826 } else {
4827 int move_up = get_next_visible_line_offset_from(p_line, -1) - 1;
4828 if (p_line - move_up > 0 && !_is_line_hidden(p_line - move_up)) {
4829 p_line -= move_up;
4830 } else {
4831 WARN_PRINT(("Caret set to hidden line " + itos(p_line) + " and there are no nonhidden lines."));
4832 }
4833 }
4834 }
4835 }
4836 bool caret_moved = get_caret_line(p_caret) != p_line;
4837 carets.write[p_caret].line = p_line;
4838
4839 int n_col = _get_char_pos_for_line(carets[p_caret].last_fit_x, p_line, p_wrap_index);
4840 if (n_col != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && p_wrap_index < get_line_wrap_count(p_line)) {
4841 Vector<String> rows = get_line_wrapped_text(p_line);
4842 int row_end_col = 0;
4843 for (int i = 0; i < p_wrap_index + 1; i++) {
4844 row_end_col += rows[i].length();
4845 }
4846 if (n_col >= row_end_col) {
4847 n_col -= 1;
4848 }
4849 }
4850 caret_moved = (caret_moved || get_caret_column(p_caret) != n_col);
4851 carets.write[p_caret].column = n_col;
4852
4853 if (is_inside_tree() && p_adjust_viewport) {
4854 adjust_viewport_to_caret(p_caret);
4855 }
4856
4857 setting_caret_line = false;
4858
4859 if (caret_moved && !caret_pos_dirty) {
4860 if (is_inside_tree()) {
4861 MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
4862 }
4863 caret_pos_dirty = true;
4864 }
4865}
4866
4867int TextEdit::get_caret_line(int p_caret) const {
4868 ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
4869 return carets[p_caret].line;
4870}
4871
4872void TextEdit::set_caret_column(int p_col, bool p_adjust_viewport, int p_caret) {
4873 ERR_FAIL_INDEX(p_caret, carets.size());
4874 if (p_col < 0) {
4875 p_col = 0;
4876 }
4877 if (p_col > get_line(get_caret_line(p_caret)).length()) {
4878 p_col = get_line(get_caret_line(p_caret)).length();
4879 }
4880
4881 bool caret_moved = get_caret_column(p_caret) != p_col;
4882 carets.write[p_caret].column = p_col;
4883
4884 carets.write[p_caret].last_fit_x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
4885
4886 if (is_inside_tree() && p_adjust_viewport) {
4887 adjust_viewport_to_caret(p_caret);
4888 }
4889
4890 if (caret_moved && !caret_pos_dirty) {
4891 if (is_inside_tree()) {
4892 MessageQueue::get_singleton()->push_call(this, "_emit_caret_changed");
4893 }
4894 caret_pos_dirty = true;
4895 }
4896}
4897
4898int TextEdit::get_caret_column(int p_caret) const {
4899 ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
4900 return carets[p_caret].column;
4901}
4902
4903int TextEdit::get_caret_wrap_index(int p_caret) const {
4904 ERR_FAIL_INDEX_V(p_caret, carets.size(), 0);
4905 return get_line_wrap_index_at_column(get_caret_line(p_caret), get_caret_column(p_caret));
4906}
4907
4908String TextEdit::get_word_under_caret(int p_caret) const {
4909 ERR_FAIL_COND_V(p_caret > carets.size(), "");
4910
4911 StringBuilder selected_text;
4912 for (int c = 0; c < carets.size(); c++) {
4913 if (p_caret != -1 && p_caret != c) {
4914 continue;
4915 }
4916
4917 PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid());
4918 for (int i = 0; i < words.size(); i = i + 2) {
4919 if (words[i] <= get_caret_column(c) && words[i + 1] > get_caret_column(c)) {
4920 selected_text += text[get_caret_line(c)].substr(words[i], words[i + 1] - words[i]);
4921 if (p_caret == -1 && c != carets.size() - 1) {
4922 selected_text += "\n";
4923 }
4924 }
4925 }
4926 }
4927 return selected_text.as_string();
4928}
4929
4930/* Selection. */
4931void TextEdit::set_selecting_enabled(const bool p_enabled) {
4932 if (selecting_enabled == p_enabled) {
4933 return;
4934 }
4935
4936 selecting_enabled = p_enabled;
4937
4938 if (!selecting_enabled) {
4939 deselect();
4940 }
4941}
4942
4943bool TextEdit::is_selecting_enabled() const {
4944 return selecting_enabled;
4945}
4946
4947void TextEdit::set_deselect_on_focus_loss_enabled(const bool p_enabled) {
4948 if (deselect_on_focus_loss_enabled == p_enabled) {
4949 return;
4950 }
4951
4952 deselect_on_focus_loss_enabled = p_enabled;
4953 if (p_enabled && has_selection() && !has_focus()) {
4954 deselect();
4955 }
4956}
4957
4958bool TextEdit::is_deselect_on_focus_loss_enabled() const {
4959 return deselect_on_focus_loss_enabled;
4960}
4961
4962void TextEdit::set_drag_and_drop_selection_enabled(const bool p_enabled) {
4963 drag_and_drop_selection_enabled = p_enabled;
4964}
4965
4966bool TextEdit::is_drag_and_drop_selection_enabled() const {
4967 return drag_and_drop_selection_enabled;
4968}
4969
4970void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) {
4971 ERR_FAIL_INDEX(p_caret, carets.size());
4972
4973 selecting_mode = p_mode;
4974 if (p_line >= 0) {
4975 ERR_FAIL_INDEX(p_line, text.size());
4976 carets.write[p_caret].selection.selecting_line = p_line;
4977 carets.write[p_caret].selection.selecting_column = CLAMP(carets[p_caret].selection.selecting_column, 0, text[carets[p_caret].selection.selecting_line].length());
4978 }
4979 if (p_column >= 0) {
4980 ERR_FAIL_INDEX(carets[p_caret].selection.selecting_line, text.size());
4981 ERR_FAIL_INDEX(p_column, text[carets[p_caret].selection.selecting_line].length() + 1);
4982 carets.write[p_caret].selection.selecting_column = p_column;
4983 }
4984}
4985
4986TextEdit::SelectionMode TextEdit::get_selection_mode() const {
4987 return selecting_mode;
4988}
4989
4990void TextEdit::select_all() {
4991 if (!selecting_enabled) {
4992 return;
4993 }
4994
4995 if (text.size() == 1 && text[0].length() == 0) {
4996 return;
4997 }
4998
4999 remove_secondary_carets();
5000 select(0, 0, text.size() - 1, text[text.size() - 1].length());
5001 set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, 0, 0);
5002 carets.write[0].selection.shiftclick_left = true;
5003 set_caret_line(get_selection_to_line(), false);
5004 set_caret_column(get_selection_to_column(), false);
5005 queue_redraw();
5006}
5007
5008void TextEdit::select_word_under_caret(int p_caret) {
5009 ERR_FAIL_COND(p_caret > carets.size());
5010
5011 if (!selecting_enabled) {
5012 return;
5013 }
5014
5015 if (text.size() == 1 && text[0].length() == 0) {
5016 return;
5017 }
5018
5019 for (int c = 0; c < carets.size(); c++) {
5020 if (p_caret != -1 && p_caret != c) {
5021 continue;
5022 }
5023
5024 if (has_selection(c)) {
5025 // Allow toggling selection by pressing the shortcut a second time.
5026 // This is also usable as a general-purpose "deselect" shortcut after
5027 // selecting anything.
5028 deselect(c);
5029 continue;
5030 }
5031
5032 int begin = 0;
5033 int end = 0;
5034 const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(c))->get_rid());
5035 for (int i = 0; i < words.size(); i = i + 2) {
5036 if ((words[i] <= get_caret_column(c) && words[i + 1] >= get_caret_column(c)) || (i == words.size() - 2 && get_caret_column(c) == words[i + 1])) {
5037 begin = words[i];
5038 end = words[i + 1];
5039 break;
5040 }
5041 }
5042
5043 // No word found.
5044 if (begin == 0 && end == 0) {
5045 continue;
5046 }
5047
5048 select(get_caret_line(c), begin, get_caret_line(c), end, c);
5049 // Move the caret to the end of the word for easier editing.
5050 set_caret_column(end, false, c);
5051 }
5052 merge_overlapping_carets();
5053}
5054
5055void TextEdit::add_selection_for_next_occurrence() {
5056 if (!selecting_enabled || !is_multiple_carets_enabled()) {
5057 return;
5058 }
5059
5060 if (text.size() == 1 && text[0].length() == 0) {
5061 return;
5062 }
5063
5064 // Always use the last caret, to correctly search for
5065 // the next occurrence that comes after this caret.
5066 int caret = get_caret_count() - 1;
5067
5068 if (!has_selection(caret)) {
5069 select_word_under_caret(caret);
5070 return;
5071 }
5072
5073 const String &highlighted_text = get_selected_text(caret);
5074 int column = get_selection_from_column(caret) + 1;
5075 int line = get_caret_line(caret);
5076
5077 const Point2i next_occurrence = search(highlighted_text, SEARCH_MATCH_CASE, line, column);
5078
5079 if (next_occurrence.x == -1 || next_occurrence.y == -1) {
5080 return;
5081 }
5082
5083 int to_column = get_selection_to_column(caret) + 1;
5084 int end = next_occurrence.x + (to_column - column);
5085 int new_caret = add_caret(next_occurrence.y, end);
5086
5087 if (new_caret != -1) {
5088 select(next_occurrence.y, next_occurrence.x, next_occurrence.y, end, new_caret);
5089 adjust_viewport_to_caret(new_caret);
5090 merge_overlapping_carets();
5091 }
5092}
5093
5094void TextEdit::select(int p_from_line, int p_from_column, int p_to_line, int p_to_column, int p_caret) {
5095 ERR_FAIL_INDEX(p_caret, carets.size());
5096 if (!selecting_enabled) {
5097 return;
5098 }
5099
5100 p_from_line = CLAMP(p_from_line, 0, text.size() - 1);
5101 p_from_column = CLAMP(p_from_column, 0, text[p_from_line].length());
5102 p_to_line = CLAMP(p_to_line, 0, text.size() - 1);
5103 p_to_column = CLAMP(p_to_column, 0, text[p_to_line].length());
5104
5105 carets.write[p_caret].selection.from_line = p_from_line;
5106 carets.write[p_caret].selection.from_column = p_from_column;
5107 carets.write[p_caret].selection.to_line = p_to_line;
5108 carets.write[p_caret].selection.to_column = p_to_column;
5109
5110 carets.write[p_caret].selection.active = true;
5111
5112 if (get_selection_from_line(p_caret) == get_selection_to_line(p_caret)) {
5113 if (get_selection_from_column(p_caret) == get_selection_to_column(p_caret)) {
5114 carets.write[p_caret].selection.active = false;
5115
5116 } else if (get_selection_from_column(p_caret) > get_selection_to_column(p_caret)) {
5117 carets.write[p_caret].selection.shiftclick_left = false;
5118 SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
5119 } else {
5120 carets.write[p_caret].selection.shiftclick_left = true;
5121 }
5122 } else if (get_selection_from_line(p_caret) > get_selection_to_line(p_caret)) {
5123 carets.write[p_caret].selection.shiftclick_left = false;
5124 SWAP(carets.write[p_caret].selection.from_line, carets.write[p_caret].selection.to_line);
5125 SWAP(carets.write[p_caret].selection.from_column, carets.write[p_caret].selection.to_column);
5126 } else {
5127 carets.write[p_caret].selection.shiftclick_left = true;
5128 }
5129
5130 caret_index_edit_dirty = true;
5131 queue_redraw();
5132}
5133
5134bool TextEdit::has_selection(int p_caret) const {
5135 ERR_FAIL_COND_V(p_caret > carets.size(), false);
5136 for (int i = 0; i < carets.size(); i++) {
5137 if (p_caret != -1 && p_caret != i) {
5138 continue;
5139 }
5140
5141 if (carets[i].selection.active) {
5142 return true;
5143 }
5144 }
5145 return false;
5146}
5147
5148String TextEdit::get_selected_text(int p_caret) {
5149 ERR_FAIL_COND_V(p_caret > carets.size(), "");
5150
5151 StringBuilder selected_text;
5152 Vector<int> caret_edit_order = get_caret_index_edit_order();
5153 for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
5154 int caret_idx = caret_edit_order[i];
5155 if (p_caret != -1 && p_caret != caret_idx) {
5156 continue;
5157 }
5158
5159 if (!has_selection(caret_idx)) {
5160 continue;
5161 }
5162 selected_text += _base_get_text(get_selection_from_line(caret_idx), get_selection_from_column(caret_idx), get_selection_to_line(caret_idx), get_selection_to_column(caret_idx));
5163 if (p_caret == -1 && i != 0) {
5164 selected_text += "\n";
5165 }
5166 }
5167
5168 return selected_text.as_string();
5169}
5170
5171int TextEdit::get_selection_line(int p_caret) const {
5172 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5173 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5174 return carets[p_caret].selection.selecting_line;
5175}
5176
5177int TextEdit::get_selection_column(int p_caret) const {
5178 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5179 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5180 return carets[p_caret].selection.selecting_column;
5181}
5182
5183int TextEdit::get_selection_from_line(int p_caret) const {
5184 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5185 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5186 return carets[p_caret].selection.from_line;
5187}
5188
5189int TextEdit::get_selection_from_column(int p_caret) const {
5190 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5191 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5192 return carets[p_caret].selection.from_column;
5193}
5194
5195int TextEdit::get_selection_to_line(int p_caret) const {
5196 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5197 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5198 return carets[p_caret].selection.to_line;
5199}
5200
5201int TextEdit::get_selection_to_column(int p_caret) const {
5202 ERR_FAIL_INDEX_V(p_caret, carets.size(), -1);
5203 ERR_FAIL_COND_V(!has_selection(p_caret), -1);
5204 return carets[p_caret].selection.to_column;
5205}
5206
5207void TextEdit::deselect(int p_caret) {
5208 ERR_FAIL_COND(p_caret > carets.size());
5209 for (int i = 0; i < carets.size(); i++) {
5210 if (p_caret != -1 && p_caret != i) {
5211 continue;
5212 }
5213 carets.write[i].selection.active = false;
5214 }
5215 caret_index_edit_dirty = true;
5216 queue_redraw();
5217}
5218
5219void TextEdit::delete_selection(int p_caret) {
5220 ERR_FAIL_COND(p_caret > carets.size());
5221
5222 begin_complex_operation();
5223 Vector<int> caret_edit_order = get_caret_index_edit_order();
5224 for (const int &i : caret_edit_order) {
5225 if (p_caret != -1 && p_caret != i) {
5226 continue;
5227 }
5228
5229 if (!has_selection(i)) {
5230 continue;
5231 }
5232
5233 selecting_mode = SelectionMode::SELECTION_MODE_NONE;
5234 _remove_text(get_selection_from_line(i), get_selection_from_column(i), get_selection_to_line(i), get_selection_to_column(i));
5235 set_caret_line(get_selection_from_line(i), false, false, 0, i);
5236 set_caret_column(get_selection_from_column(i), i == 0, i);
5237 carets.write[i].selection.active = false;
5238
5239 adjust_carets_after_edit(i, carets[i].selection.from_line, carets[i].selection.from_column, carets[i].selection.to_line, carets[i].selection.to_column);
5240 }
5241 end_complex_operation();
5242 queue_redraw();
5243}
5244
5245/* Line wrapping. */
5246void TextEdit::set_line_wrapping_mode(LineWrappingMode p_wrapping_mode) {
5247 if (line_wrapping_mode != p_wrapping_mode) {
5248 line_wrapping_mode = p_wrapping_mode;
5249 _update_wrap_at_column(true);
5250 queue_redraw();
5251 }
5252}
5253
5254TextEdit::LineWrappingMode TextEdit::get_line_wrapping_mode() const {
5255 return line_wrapping_mode;
5256}
5257
5258void TextEdit::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
5259 if (autowrap_mode == p_mode) {
5260 return;
5261 }
5262
5263 autowrap_mode = p_mode;
5264 if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) {
5265 _update_wrap_at_column(true);
5266 queue_redraw();
5267 }
5268}
5269
5270TextServer::AutowrapMode TextEdit::get_autowrap_mode() const {
5271 return autowrap_mode;
5272}
5273
5274bool TextEdit::is_line_wrapped(int p_line) const {
5275 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
5276 if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
5277 return false;
5278 }
5279 return text.get_line_wrap_amount(p_line) > 0;
5280}
5281
5282int TextEdit::get_line_wrap_count(int p_line) const {
5283 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
5284
5285 if (!is_line_wrapped(p_line)) {
5286 return 0;
5287 }
5288
5289 return text.get_line_wrap_amount(p_line);
5290}
5291
5292int TextEdit::get_line_wrap_index_at_column(int p_line, int p_column) const {
5293 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
5294 ERR_FAIL_COND_V(p_column < 0, 0);
5295 ERR_FAIL_COND_V(p_column > text[p_line].length(), 0);
5296
5297 if (!is_line_wrapped(p_line)) {
5298 return 0;
5299 }
5300
5301 /* Loop through wraps in the line text until we get to the column. */
5302 int wrap_index = 0;
5303 int col = 0;
5304 Vector<String> lines = get_line_wrapped_text(p_line);
5305 for (int i = 0; i < lines.size(); i++) {
5306 wrap_index = i;
5307 String s = lines[wrap_index];
5308 col += s.length();
5309 if (col > p_column) {
5310 break;
5311 }
5312 }
5313 return wrap_index;
5314}
5315
5316Vector<String> TextEdit::get_line_wrapped_text(int p_line) const {
5317 ERR_FAIL_INDEX_V(p_line, text.size(), Vector<String>());
5318
5319 Vector<String> lines;
5320 if (!is_line_wrapped(p_line)) {
5321 lines.push_back(text[p_line]);
5322 return lines;
5323 }
5324
5325 const String &line_text = text[p_line];
5326 Vector<Vector2i> line_ranges = text.get_line_wrap_ranges(p_line);
5327 for (int i = 0; i < line_ranges.size(); i++) {
5328 lines.push_back(line_text.substr(line_ranges[i].x, line_ranges[i].y - line_ranges[i].x));
5329 }
5330
5331 return lines;
5332}
5333
5334/* Viewport */
5335// Scrolling.
5336void TextEdit::set_smooth_scroll_enabled(const bool p_enabled) {
5337 v_scroll->set_smooth_scroll_enabled(p_enabled);
5338 smooth_scroll_enabled = p_enabled;
5339}
5340
5341bool TextEdit::is_smooth_scroll_enabled() const {
5342 return smooth_scroll_enabled;
5343}
5344
5345void TextEdit::set_scroll_past_end_of_file_enabled(const bool p_enabled) {
5346 if (scroll_past_end_of_file_enabled == p_enabled) {
5347 return;
5348 }
5349
5350 scroll_past_end_of_file_enabled = p_enabled;
5351 queue_redraw();
5352}
5353
5354bool TextEdit::is_scroll_past_end_of_file_enabled() const {
5355 return scroll_past_end_of_file_enabled;
5356}
5357
5358VScrollBar *TextEdit::get_v_scroll_bar() const {
5359 return v_scroll;
5360}
5361
5362HScrollBar *TextEdit::get_h_scroll_bar() const {
5363 return h_scroll;
5364}
5365
5366void TextEdit::set_v_scroll(double p_scroll) {
5367 v_scroll->set_value(p_scroll);
5368 int max_v_scroll = v_scroll->get_max() - v_scroll->get_page();
5369 if (p_scroll >= max_v_scroll - 1.0) {
5370 _scroll_moved(v_scroll->get_value());
5371 }
5372}
5373
5374double TextEdit::get_v_scroll() const {
5375 return v_scroll->get_value();
5376}
5377
5378void TextEdit::set_h_scroll(int p_scroll) {
5379 if (p_scroll < 0) {
5380 p_scroll = 0;
5381 }
5382 h_scroll->set_value(p_scroll);
5383}
5384
5385int TextEdit::get_h_scroll() const {
5386 return h_scroll->get_value();
5387}
5388
5389void TextEdit::set_v_scroll_speed(float p_speed) {
5390 // Prevent setting a vertical scroll speed value under 1.
5391 ERR_FAIL_COND(p_speed < 1.0);
5392 v_scroll_speed = p_speed;
5393}
5394
5395float TextEdit::get_v_scroll_speed() const {
5396 return v_scroll_speed;
5397}
5398
5399void TextEdit::set_fit_content_height_enabled(const bool p_enabled) {
5400 if (fit_content_height == p_enabled) {
5401 return;
5402 }
5403 fit_content_height = p_enabled;
5404 update_minimum_size();
5405}
5406
5407bool TextEdit::is_fit_content_height_enabled() const {
5408 return fit_content_height;
5409}
5410
5411double TextEdit::get_scroll_pos_for_line(int p_line, int p_wrap_index) const {
5412 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
5413 ERR_FAIL_COND_V(p_wrap_index < 0, 0);
5414 ERR_FAIL_COND_V(p_wrap_index > get_line_wrap_count(p_line), 0);
5415
5416 if (get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE && !_is_hiding_enabled()) {
5417 return p_line;
5418 }
5419
5420 double new_line_scroll_pos = 0.0;
5421 if (p_line > 0) {
5422 new_line_scroll_pos = get_visible_line_count_in_range(0, MIN(p_line - 1, text.size() - 1));
5423 }
5424 new_line_scroll_pos += p_wrap_index;
5425 return new_line_scroll_pos;
5426}
5427
5428// Visible lines.
5429void TextEdit::set_line_as_first_visible(int p_line, int p_wrap_index) {
5430 ERR_FAIL_INDEX(p_line, text.size());
5431 ERR_FAIL_COND(p_wrap_index < 0);
5432 ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line));
5433 set_v_scroll(get_scroll_pos_for_line(p_line, p_wrap_index));
5434}
5435
5436int TextEdit::get_first_visible_line() const {
5437 return CLAMP(first_visible_line, 0, text.size() - 1);
5438}
5439
5440void TextEdit::set_line_as_center_visible(int p_line, int p_wrap_index) {
5441 ERR_FAIL_INDEX(p_line, text.size());
5442 ERR_FAIL_COND(p_wrap_index < 0);
5443 ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line));
5444
5445 int visible_rows = get_visible_line_count();
5446 Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, (-visible_rows / 2) - 1);
5447 int first_line = p_line - next_line.x + 1;
5448
5449 if (first_line < 0) {
5450 set_v_scroll(0);
5451 return;
5452 }
5453 set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y));
5454}
5455
5456void TextEdit::set_line_as_last_visible(int p_line, int p_wrap_index) {
5457 ERR_FAIL_INDEX(p_line, text.size());
5458 ERR_FAIL_COND(p_wrap_index < 0);
5459 ERR_FAIL_COND(p_wrap_index > get_line_wrap_count(p_line));
5460
5461 Point2i next_line = get_next_visible_line_index_offset_from(p_line, p_wrap_index, -get_visible_line_count() - 1);
5462 int first_line = p_line - next_line.x + 1;
5463
5464 // Adding _get_visible_lines_offset is not 100% correct as we end up showing almost p_line + 1, however, it provides a
5465 // better user experience. Therefore we need to special case < visible line count, else showing line 0 is impossible.
5466 if (get_visible_line_count_in_range(0, p_line) < get_visible_line_count() + 1) {
5467 set_v_scroll(0);
5468 return;
5469 }
5470 set_v_scroll(get_scroll_pos_for_line(first_line, next_line.y) + _get_visible_lines_offset());
5471}
5472
5473int TextEdit::get_last_full_visible_line() const {
5474 int first_vis_line = get_first_visible_line();
5475 int last_vis_line = 0;
5476 last_vis_line = first_vis_line + get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).x - 1;
5477 last_vis_line = CLAMP(last_vis_line, 0, text.size() - 1);
5478 return last_vis_line;
5479}
5480
5481int TextEdit::get_last_full_visible_line_wrap_index() const {
5482 int first_vis_line = get_first_visible_line();
5483 return get_next_visible_line_index_offset_from(first_vis_line, first_visible_line_wrap_ofs, get_visible_line_count()).y;
5484}
5485
5486int TextEdit::get_visible_line_count() const {
5487 return _get_control_height() / get_line_height();
5488}
5489
5490int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) const {
5491 ERR_FAIL_INDEX_V(p_from_line, text.size(), 0);
5492 ERR_FAIL_INDEX_V(p_to_line, text.size(), 0);
5493
5494 // So we can handle inputs in whatever order.
5495 if (p_from_line > p_to_line) {
5496 SWAP(p_from_line, p_to_line);
5497 }
5498
5499 // Returns the total number of (lines + wrapped - hidden).
5500 if (!_is_hiding_enabled() && get_line_wrapping_mode() == LineWrappingMode::LINE_WRAPPING_NONE) {
5501 return (p_to_line - p_from_line) + 1;
5502 }
5503
5504 int total_rows = 0;
5505 for (int i = p_from_line; i <= p_to_line; i++) {
5506 if (!text.is_hidden(i)) {
5507 total_rows++;
5508 total_rows += get_line_wrap_count(i);
5509 }
5510 }
5511 return total_rows;
5512}
5513
5514int TextEdit::get_total_visible_line_count() const {
5515 return get_visible_line_count_in_range(0, text.size() - 1);
5516}
5517
5518// Auto adjust.
5519void TextEdit::adjust_viewport_to_caret(int p_caret) {
5520 ERR_FAIL_INDEX(p_caret, carets.size());
5521
5522 // Make sure Caret is visible on the screen.
5523 scrolling = false;
5524 minimap_clicked = false;
5525
5526 int cur_line = get_caret_line(p_caret);
5527 int cur_wrap = get_caret_wrap_index(p_caret);
5528
5529 int first_vis_line = get_first_visible_line();
5530 int first_vis_wrap = first_visible_line_wrap_ofs;
5531 int last_vis_line = get_last_full_visible_line();
5532 int last_vis_wrap = get_last_full_visible_line_wrap_index();
5533
5534 if (cur_line < first_vis_line || (cur_line == first_vis_line && cur_wrap < first_vis_wrap)) {
5535 // Caret is above screen.
5536 set_line_as_first_visible(cur_line, cur_wrap);
5537 } else if (cur_line > last_vis_line || (cur_line == last_vis_line && cur_wrap > last_vis_wrap)) {
5538 // Caret is below screen.
5539 set_line_as_last_visible(cur_line, cur_wrap);
5540 }
5541
5542 int visible_width = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
5543 if (draw_minimap) {
5544 visible_width -= minimap_width;
5545 }
5546 if (v_scroll->is_visible_in_tree()) {
5547 visible_width -= v_scroll->get_combined_minimum_size().width;
5548 }
5549 visible_width -= 20; // Give it a little more space.
5550
5551 Vector2i caret_pos;
5552
5553 // Get position of the start of caret.
5554 if (ime_text.length() != 0 && ime_selection.x != 0) {
5555 caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
5556 } else {
5557 caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
5558 }
5559
5560 // Get position of the end of caret.
5561 if (ime_text.length() != 0) {
5562 if (ime_selection.y != 0) {
5563 caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
5564 } else {
5565 caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));
5566 }
5567 } else {
5568 caret_pos.y = caret_pos.x;
5569 }
5570
5571 if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) {
5572 first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
5573 }
5574
5575 if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) {
5576 first_visible_col = MIN(caret_pos.x, caret_pos.y);
5577 }
5578 h_scroll->set_value(first_visible_col);
5579
5580 queue_redraw();
5581}
5582
5583void TextEdit::center_viewport_to_caret(int p_caret) {
5584 ERR_FAIL_INDEX(p_caret, carets.size());
5585
5586 // Move viewport so the caret is in the center of the screen.
5587 scrolling = false;
5588 minimap_clicked = false;
5589
5590 set_line_as_center_visible(get_caret_line(p_caret), get_caret_wrap_index(p_caret));
5591 int visible_width = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
5592 if (draw_minimap) {
5593 visible_width -= minimap_width;
5594 }
5595 if (v_scroll->is_visible_in_tree()) {
5596 visible_width -= v_scroll->get_combined_minimum_size().width;
5597 }
5598 visible_width -= 20; // Give it a little more space.
5599
5600 if (get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE) {
5601 // Center x offset.
5602
5603 Vector2i caret_pos;
5604
5605 // Get position of the start of caret.
5606 if (ime_text.length() != 0 && ime_selection.x != 0) {
5607 caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x, get_caret_line(p_caret), get_caret_column(p_caret));
5608 } else {
5609 caret_pos.x = _get_column_x_offset_for_line(get_caret_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret));
5610 }
5611
5612 // Get position of the end of caret.
5613 if (ime_text.length() != 0) {
5614 if (ime_selection.y != 0) {
5615 caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_selection.x + ime_selection.y, get_caret_line(p_caret), get_caret_column(p_caret));
5616 } else {
5617 caret_pos.y = _get_column_x_offset_for_line(get_caret_column(p_caret) + ime_text.size(), get_caret_line(p_caret), get_caret_column(p_caret));
5618 }
5619 } else {
5620 caret_pos.y = caret_pos.x;
5621 }
5622
5623 if (MAX(caret_pos.x, caret_pos.y) > (first_visible_col + visible_width)) {
5624 first_visible_col = MAX(caret_pos.x, caret_pos.y) - visible_width + 1;
5625 }
5626
5627 if (MIN(caret_pos.x, caret_pos.y) < first_visible_col) {
5628 first_visible_col = MIN(caret_pos.x, caret_pos.y);
5629 }
5630 } else {
5631 first_visible_col = 0;
5632 }
5633 h_scroll->set_value(first_visible_col);
5634
5635 queue_redraw();
5636}
5637
5638/* Minimap */
5639void TextEdit::set_draw_minimap(bool p_enabled) {
5640 if (draw_minimap == p_enabled) {
5641 return;
5642 }
5643
5644 draw_minimap = p_enabled;
5645 _update_wrap_at_column();
5646 queue_redraw();
5647}
5648
5649bool TextEdit::is_drawing_minimap() const {
5650 return draw_minimap;
5651}
5652
5653void TextEdit::set_minimap_width(int p_minimap_width) {
5654 if (minimap_width == p_minimap_width) {
5655 return;
5656 }
5657
5658 minimap_width = p_minimap_width;
5659 _update_wrap_at_column();
5660 queue_redraw();
5661}
5662
5663int TextEdit::get_minimap_width() const {
5664 return minimap_width;
5665}
5666
5667int TextEdit::get_minimap_visible_lines() const {
5668 return _get_control_height() / (minimap_char_size.y + minimap_line_spacing);
5669}
5670
5671/* Gutters. */
5672void TextEdit::add_gutter(int p_at) {
5673 if (p_at < 0 || p_at > gutters.size()) {
5674 gutters.push_back(GutterInfo());
5675 } else {
5676 gutters.insert(p_at, GutterInfo());
5677 }
5678
5679 text.add_gutter(p_at);
5680
5681 _update_gutter_width();
5682
5683 emit_signal(SNAME("gutter_added"));
5684 queue_redraw();
5685}
5686
5687void TextEdit::remove_gutter(int p_gutter) {
5688 ERR_FAIL_INDEX(p_gutter, gutters.size());
5689
5690 gutters.remove_at(p_gutter);
5691
5692 text.remove_gutter(p_gutter);
5693
5694 _update_gutter_width();
5695
5696 emit_signal(SNAME("gutter_removed"));
5697 queue_redraw();
5698}
5699
5700int TextEdit::get_gutter_count() const {
5701 return gutters.size();
5702}
5703
5704void TextEdit::set_gutter_name(int p_gutter, const String &p_name) {
5705 ERR_FAIL_INDEX(p_gutter, gutters.size());
5706 gutters.write[p_gutter].name = p_name;
5707}
5708
5709String TextEdit::get_gutter_name(int p_gutter) const {
5710 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), "");
5711 return gutters[p_gutter].name;
5712}
5713
5714void TextEdit::set_gutter_type(int p_gutter, GutterType p_type) {
5715 ERR_FAIL_INDEX(p_gutter, gutters.size());
5716
5717 if (gutters[p_gutter].type == p_type) {
5718 return;
5719 }
5720
5721 gutters.write[p_gutter].type = p_type;
5722 queue_redraw();
5723}
5724
5725TextEdit::GutterType TextEdit::get_gutter_type(int p_gutter) const {
5726 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), GUTTER_TYPE_STRING);
5727 return gutters[p_gutter].type;
5728}
5729
5730void TextEdit::set_gutter_width(int p_gutter, int p_width) {
5731 ERR_FAIL_INDEX(p_gutter, gutters.size());
5732 if (gutters[p_gutter].width == p_width) {
5733 return;
5734 }
5735 gutters.write[p_gutter].width = p_width;
5736 _update_gutter_width();
5737}
5738
5739int TextEdit::get_gutter_width(int p_gutter) const {
5740 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), -1);
5741 return gutters[p_gutter].width;
5742}
5743
5744int TextEdit::get_total_gutter_width() const {
5745 return gutters_width + gutter_padding;
5746}
5747
5748void TextEdit::set_gutter_draw(int p_gutter, bool p_draw) {
5749 ERR_FAIL_INDEX(p_gutter, gutters.size());
5750 if (gutters[p_gutter].draw == p_draw) {
5751 return;
5752 }
5753 gutters.write[p_gutter].draw = p_draw;
5754 _update_gutter_width();
5755}
5756
5757bool TextEdit::is_gutter_drawn(int p_gutter) const {
5758 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false);
5759 return gutters[p_gutter].draw;
5760}
5761
5762void TextEdit::set_gutter_clickable(int p_gutter, bool p_clickable) {
5763 ERR_FAIL_INDEX(p_gutter, gutters.size());
5764
5765 if (gutters[p_gutter].clickable == p_clickable) {
5766 return;
5767 }
5768
5769 gutters.write[p_gutter].clickable = p_clickable;
5770 queue_redraw();
5771}
5772
5773bool TextEdit::is_gutter_clickable(int p_gutter) const {
5774 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false);
5775 return gutters[p_gutter].clickable;
5776}
5777
5778void TextEdit::set_gutter_overwritable(int p_gutter, bool p_overwritable) {
5779 ERR_FAIL_INDEX(p_gutter, gutters.size());
5780 gutters.write[p_gutter].overwritable = p_overwritable;
5781}
5782
5783bool TextEdit::is_gutter_overwritable(int p_gutter) const {
5784 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false);
5785 return gutters[p_gutter].overwritable;
5786}
5787
5788void TextEdit::merge_gutters(int p_from_line, int p_to_line) {
5789 ERR_FAIL_INDEX(p_from_line, text.size());
5790 ERR_FAIL_INDEX(p_to_line, text.size());
5791 if (p_from_line == p_to_line) {
5792 return;
5793 }
5794
5795 for (int i = 0; i < gutters.size(); i++) {
5796 if (!gutters[i].overwritable) {
5797 continue;
5798 }
5799
5800 if (text.get_line_gutter_text(p_from_line, i) != "") {
5801 text.set_line_gutter_text(p_to_line, i, text.get_line_gutter_text(p_from_line, i));
5802 text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
5803 }
5804
5805 if (text.get_line_gutter_icon(p_from_line, i).is_valid()) {
5806 text.set_line_gutter_icon(p_to_line, i, text.get_line_gutter_icon(p_from_line, i));
5807 text.set_line_gutter_item_color(p_to_line, i, text.get_line_gutter_item_color(p_from_line, i));
5808 }
5809
5810 if (text.get_line_gutter_metadata(p_from_line, i) != "") {
5811 text.set_line_gutter_metadata(p_to_line, i, text.get_line_gutter_metadata(p_from_line, i));
5812 }
5813
5814 if (text.is_line_gutter_clickable(p_from_line, i)) {
5815 text.set_line_gutter_clickable(p_to_line, i, true);
5816 }
5817 }
5818 queue_redraw();
5819}
5820
5821void TextEdit::set_gutter_custom_draw(int p_gutter, const Callable &p_draw_callback) {
5822 ERR_FAIL_INDEX(p_gutter, gutters.size());
5823
5824 if (gutters[p_gutter].custom_draw_callback == p_draw_callback) {
5825 return;
5826 }
5827
5828 gutters.write[p_gutter].custom_draw_callback = p_draw_callback;
5829 queue_redraw();
5830}
5831
5832// Line gutters.
5833void TextEdit::set_line_gutter_metadata(int p_line, int p_gutter, const Variant &p_metadata) {
5834 ERR_FAIL_INDEX(p_line, text.size());
5835 ERR_FAIL_INDEX(p_gutter, gutters.size());
5836 text.set_line_gutter_metadata(p_line, p_gutter, p_metadata);
5837}
5838
5839Variant TextEdit::get_line_gutter_metadata(int p_line, int p_gutter) const {
5840 ERR_FAIL_INDEX_V(p_line, text.size(), "");
5841 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), "");
5842 return text.get_line_gutter_metadata(p_line, p_gutter);
5843}
5844
5845void TextEdit::set_line_gutter_text(int p_line, int p_gutter, const String &p_text) {
5846 ERR_FAIL_INDEX(p_line, text.size());
5847 ERR_FAIL_INDEX(p_gutter, gutters.size());
5848
5849 if (text.get_line_gutter_text(p_line, p_gutter) == p_text) {
5850 return;
5851 }
5852
5853 text.set_line_gutter_text(p_line, p_gutter, p_text);
5854 queue_redraw();
5855}
5856
5857String TextEdit::get_line_gutter_text(int p_line, int p_gutter) const {
5858 ERR_FAIL_INDEX_V(p_line, text.size(), "");
5859 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), "");
5860 return text.get_line_gutter_text(p_line, p_gutter);
5861}
5862
5863void TextEdit::set_line_gutter_icon(int p_line, int p_gutter, const Ref<Texture2D> &p_icon) {
5864 ERR_FAIL_INDEX(p_line, text.size());
5865 ERR_FAIL_INDEX(p_gutter, gutters.size());
5866
5867 if (text.get_line_gutter_icon(p_line, p_gutter) == p_icon) {
5868 return;
5869 }
5870
5871 text.set_line_gutter_icon(p_line, p_gutter, p_icon);
5872 queue_redraw();
5873}
5874
5875Ref<Texture2D> TextEdit::get_line_gutter_icon(int p_line, int p_gutter) const {
5876 ERR_FAIL_INDEX_V(p_line, text.size(), Ref<Texture2D>());
5877 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), Ref<Texture2D>());
5878 return text.get_line_gutter_icon(p_line, p_gutter);
5879}
5880
5881void TextEdit::set_line_gutter_item_color(int p_line, int p_gutter, const Color &p_color) {
5882 ERR_FAIL_INDEX(p_line, text.size());
5883 ERR_FAIL_INDEX(p_gutter, gutters.size());
5884
5885 if (text.get_line_gutter_item_color(p_line, p_gutter) == p_color) {
5886 return;
5887 }
5888
5889 text.set_line_gutter_item_color(p_line, p_gutter, p_color);
5890 queue_redraw();
5891}
5892
5893Color TextEdit::get_line_gutter_item_color(int p_line, int p_gutter) const {
5894 ERR_FAIL_INDEX_V(p_line, text.size(), Color());
5895 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), Color());
5896 return text.get_line_gutter_item_color(p_line, p_gutter);
5897}
5898
5899void TextEdit::set_line_gutter_clickable(int p_line, int p_gutter, bool p_clickable) {
5900 ERR_FAIL_INDEX(p_line, text.size());
5901 ERR_FAIL_INDEX(p_gutter, gutters.size());
5902 text.set_line_gutter_clickable(p_line, p_gutter, p_clickable);
5903}
5904
5905bool TextEdit::is_line_gutter_clickable(int p_line, int p_gutter) const {
5906 ERR_FAIL_INDEX_V(p_line, text.size(), false);
5907 ERR_FAIL_INDEX_V(p_gutter, gutters.size(), false);
5908 return text.is_line_gutter_clickable(p_line, p_gutter);
5909}
5910
5911// Line style
5912void TextEdit::set_line_background_color(int p_line, const Color &p_color) {
5913 ERR_FAIL_INDEX(p_line, text.size());
5914
5915 if (text.get_line_background_color(p_line) == p_color) {
5916 return;
5917 }
5918
5919 text.set_line_background_color(p_line, p_color);
5920 queue_redraw();
5921}
5922
5923Color TextEdit::get_line_background_color(int p_line) const {
5924 ERR_FAIL_INDEX_V(p_line, text.size(), Color());
5925 return text.get_line_background_color(p_line);
5926}
5927
5928/* Syntax Highlighting. */
5929void TextEdit::set_syntax_highlighter(Ref<SyntaxHighlighter> p_syntax_highlighter) {
5930 if (syntax_highlighter == p_syntax_highlighter && syntax_highlighter.is_valid() == p_syntax_highlighter.is_valid()) {
5931 return;
5932 }
5933
5934 syntax_highlighter = p_syntax_highlighter;
5935 if (syntax_highlighter.is_valid()) {
5936 syntax_highlighter->set_text_edit(this);
5937 }
5938 queue_redraw();
5939}
5940
5941Ref<SyntaxHighlighter> TextEdit::get_syntax_highlighter() const {
5942 return syntax_highlighter;
5943}
5944
5945/* Visual. */
5946void TextEdit::set_highlight_current_line(bool p_enabled) {
5947 if (highlight_current_line == p_enabled) {
5948 return;
5949 }
5950
5951 highlight_current_line = p_enabled;
5952 queue_redraw();
5953}
5954
5955bool TextEdit::is_highlight_current_line_enabled() const {
5956 return highlight_current_line;
5957}
5958
5959void TextEdit::set_highlight_all_occurrences(const bool p_enabled) {
5960 if (highlight_all_occurrences == p_enabled) {
5961 return;
5962 }
5963
5964 highlight_all_occurrences = p_enabled;
5965 queue_redraw();
5966}
5967
5968bool TextEdit::is_highlight_all_occurrences_enabled() const {
5969 return highlight_all_occurrences;
5970}
5971
5972void TextEdit::set_draw_control_chars(bool p_enabled) {
5973 if (draw_control_chars != p_enabled) {
5974 draw_control_chars = p_enabled;
5975 if (menu) {
5976 menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
5977 }
5978 text.set_draw_control_chars(draw_control_chars);
5979 text.invalidate_font();
5980 _update_placeholder();
5981 queue_redraw();
5982 }
5983}
5984
5985bool TextEdit::get_draw_control_chars() const {
5986 return draw_control_chars;
5987}
5988
5989void TextEdit::set_draw_tabs(bool p_enabled) {
5990 if (draw_tabs == p_enabled) {
5991 return;
5992 }
5993
5994 draw_tabs = p_enabled;
5995 queue_redraw();
5996}
5997
5998bool TextEdit::is_drawing_tabs() const {
5999 return draw_tabs;
6000}
6001
6002void TextEdit::set_draw_spaces(bool p_enabled) {
6003 if (draw_spaces == p_enabled) {
6004 return;
6005 }
6006
6007 draw_spaces = p_enabled;
6008 queue_redraw();
6009}
6010
6011bool TextEdit::is_drawing_spaces() const {
6012 return draw_spaces;
6013}
6014
6015void TextEdit::_bind_methods() {
6016 /* Internal. */
6017
6018 ClassDB::bind_method(D_METHOD("_text_changed_emit"), &TextEdit::_text_changed_emit);
6019
6020 /* Text */
6021 // Text properties
6022 ClassDB::bind_method(D_METHOD("has_ime_text"), &TextEdit::has_ime_text);
6023
6024 ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &TextEdit::set_editable);
6025 ClassDB::bind_method(D_METHOD("is_editable"), &TextEdit::is_editable);
6026
6027 ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextEdit::set_text_direction);
6028 ClassDB::bind_method(D_METHOD("get_text_direction"), &TextEdit::get_text_direction);
6029
6030 ClassDB::bind_method(D_METHOD("set_language", "language"), &TextEdit::set_language);
6031 ClassDB::bind_method(D_METHOD("get_language"), &TextEdit::get_language);
6032
6033 ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextEdit::set_structured_text_bidi_override);
6034 ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextEdit::get_structured_text_bidi_override);
6035 ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextEdit::set_structured_text_bidi_override_options);
6036 ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextEdit::get_structured_text_bidi_override_options);
6037
6038 ClassDB::bind_method(D_METHOD("set_tab_size", "size"), &TextEdit::set_tab_size);
6039 ClassDB::bind_method(D_METHOD("get_tab_size"), &TextEdit::get_tab_size);
6040
6041 // User controls
6042 ClassDB::bind_method(D_METHOD("set_overtype_mode_enabled", "enabled"), &TextEdit::set_overtype_mode_enabled);
6043 ClassDB::bind_method(D_METHOD("is_overtype_mode_enabled"), &TextEdit::is_overtype_mode_enabled);
6044
6045 ClassDB::bind_method(D_METHOD("set_context_menu_enabled", "enabled"), &TextEdit::set_context_menu_enabled);
6046 ClassDB::bind_method(D_METHOD("is_context_menu_enabled"), &TextEdit::is_context_menu_enabled);
6047
6048 ClassDB::bind_method(D_METHOD("set_shortcut_keys_enabled", "enabled"), &TextEdit::set_shortcut_keys_enabled);
6049 ClassDB::bind_method(D_METHOD("is_shortcut_keys_enabled"), &TextEdit::is_shortcut_keys_enabled);
6050
6051 ClassDB::bind_method(D_METHOD("set_virtual_keyboard_enabled", "enabled"), &TextEdit::set_virtual_keyboard_enabled);
6052 ClassDB::bind_method(D_METHOD("is_virtual_keyboard_enabled"), &TextEdit::is_virtual_keyboard_enabled);
6053
6054 ClassDB::bind_method(D_METHOD("set_middle_mouse_paste_enabled", "enabled"), &TextEdit::set_middle_mouse_paste_enabled);
6055 ClassDB::bind_method(D_METHOD("is_middle_mouse_paste_enabled"), &TextEdit::is_middle_mouse_paste_enabled);
6056
6057 // Text manipulation
6058 ClassDB::bind_method(D_METHOD("clear"), &TextEdit::clear);
6059
6060 ClassDB::bind_method(D_METHOD("set_text", "text"), &TextEdit::set_text);
6061 ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text);
6062
6063 ClassDB::bind_method(D_METHOD("get_line_count"), &TextEdit::get_line_count);
6064
6065 ClassDB::bind_method(D_METHOD("set_placeholder", "text"), &TextEdit::set_placeholder);
6066 ClassDB::bind_method(D_METHOD("get_placeholder"), &TextEdit::get_placeholder);
6067
6068 ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line);
6069 ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
6070
6071 ClassDB::bind_method(D_METHOD("get_line_width", "line", "wrap_index"), &TextEdit::get_line_width, DEFVAL(-1));
6072 ClassDB::bind_method(D_METHOD("get_line_height"), &TextEdit::get_line_height);
6073
6074 ClassDB::bind_method(D_METHOD("get_indent_level", "line"), &TextEdit::get_indent_level);
6075 ClassDB::bind_method(D_METHOD("get_first_non_whitespace_column", "line"), &TextEdit::get_first_non_whitespace_column);
6076
6077 ClassDB::bind_method(D_METHOD("swap_lines", "from_line", "to_line"), &TextEdit::swap_lines);
6078
6079 ClassDB::bind_method(D_METHOD("insert_line_at", "line", "text"), &TextEdit::insert_line_at);
6080 ClassDB::bind_method(D_METHOD("insert_text_at_caret", "text", "caret_index"), &TextEdit::insert_text_at_caret, DEFVAL(-1));
6081
6082 ClassDB::bind_method(D_METHOD("remove_text", "from_line", "from_column", "to_line", "to_column"), &TextEdit::remove_text);
6083
6084 ClassDB::bind_method(D_METHOD("get_last_unhidden_line"), &TextEdit::get_last_unhidden_line);
6085 ClassDB::bind_method(D_METHOD("get_next_visible_line_offset_from", "line", "visible_amount"), &TextEdit::get_next_visible_line_offset_from);
6086 ClassDB::bind_method(D_METHOD("get_next_visible_line_index_offset_from", "line", "wrap_index", "visible_amount"), &TextEdit::get_next_visible_line_index_offset_from);
6087
6088 // Overridable actions
6089 ClassDB::bind_method(D_METHOD("backspace", "caret_index"), &TextEdit::backspace, DEFVAL(-1));
6090
6091 ClassDB::bind_method(D_METHOD("cut", "caret_index"), &TextEdit::cut, DEFVAL(-1));
6092 ClassDB::bind_method(D_METHOD("copy", "caret_index"), &TextEdit::copy, DEFVAL(-1));
6093 ClassDB::bind_method(D_METHOD("paste", "caret_index"), &TextEdit::paste, DEFVAL(-1));
6094 ClassDB::bind_method(D_METHOD("paste_primary_clipboard", "caret_index"), &TextEdit::paste_primary_clipboard, DEFVAL(-1));
6095
6096 GDVIRTUAL_BIND(_handle_unicode_input, "unicode_char", "caret_index")
6097 GDVIRTUAL_BIND(_backspace, "caret_index")
6098 GDVIRTUAL_BIND(_cut, "caret_index")
6099 GDVIRTUAL_BIND(_copy, "caret_index")
6100 GDVIRTUAL_BIND(_paste, "caret_index")
6101 GDVIRTUAL_BIND(_paste_primary_clipboard, "caret_index")
6102
6103 // Context Menu
6104 BIND_ENUM_CONSTANT(MENU_CUT);
6105 BIND_ENUM_CONSTANT(MENU_COPY);
6106 BIND_ENUM_CONSTANT(MENU_PASTE);
6107 BIND_ENUM_CONSTANT(MENU_CLEAR);
6108 BIND_ENUM_CONSTANT(MENU_SELECT_ALL);
6109 BIND_ENUM_CONSTANT(MENU_UNDO);
6110 BIND_ENUM_CONSTANT(MENU_REDO);
6111 BIND_ENUM_CONSTANT(MENU_SUBMENU_TEXT_DIR);
6112 BIND_ENUM_CONSTANT(MENU_DIR_INHERITED);
6113 BIND_ENUM_CONSTANT(MENU_DIR_AUTO);
6114 BIND_ENUM_CONSTANT(MENU_DIR_LTR);
6115 BIND_ENUM_CONSTANT(MENU_DIR_RTL);
6116 BIND_ENUM_CONSTANT(MENU_DISPLAY_UCC);
6117 BIND_ENUM_CONSTANT(MENU_SUBMENU_INSERT_UCC);
6118 BIND_ENUM_CONSTANT(MENU_INSERT_LRM);
6119 BIND_ENUM_CONSTANT(MENU_INSERT_RLM);
6120 BIND_ENUM_CONSTANT(MENU_INSERT_LRE);
6121 BIND_ENUM_CONSTANT(MENU_INSERT_RLE);
6122 BIND_ENUM_CONSTANT(MENU_INSERT_LRO);
6123 BIND_ENUM_CONSTANT(MENU_INSERT_RLO);
6124 BIND_ENUM_CONSTANT(MENU_INSERT_PDF);
6125 BIND_ENUM_CONSTANT(MENU_INSERT_ALM);
6126 BIND_ENUM_CONSTANT(MENU_INSERT_LRI);
6127 BIND_ENUM_CONSTANT(MENU_INSERT_RLI);
6128 BIND_ENUM_CONSTANT(MENU_INSERT_FSI);
6129 BIND_ENUM_CONSTANT(MENU_INSERT_PDI);
6130 BIND_ENUM_CONSTANT(MENU_INSERT_ZWJ);
6131 BIND_ENUM_CONSTANT(MENU_INSERT_ZWNJ);
6132 BIND_ENUM_CONSTANT(MENU_INSERT_WJ);
6133 BIND_ENUM_CONSTANT(MENU_INSERT_SHY);
6134 BIND_ENUM_CONSTANT(MENU_MAX);
6135
6136 /* Versioning */
6137 BIND_ENUM_CONSTANT(ACTION_NONE);
6138 BIND_ENUM_CONSTANT(ACTION_TYPING);
6139 BIND_ENUM_CONSTANT(ACTION_BACKSPACE);
6140 BIND_ENUM_CONSTANT(ACTION_DELETE);
6141
6142 ClassDB::bind_method(D_METHOD("start_action", "action"), &TextEdit::start_action);
6143 ClassDB::bind_method(D_METHOD("end_action"), &TextEdit::end_complex_operation);
6144
6145 ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation);
6146 ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation);
6147
6148 ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo);
6149 ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo);
6150 ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo);
6151 ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo);
6152 ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history);
6153
6154 ClassDB::bind_method(D_METHOD("tag_saved_version"), &TextEdit::tag_saved_version);
6155
6156 ClassDB::bind_method(D_METHOD("get_version"), &TextEdit::get_version);
6157 ClassDB::bind_method(D_METHOD("get_saved_version"), &TextEdit::get_saved_version);
6158
6159 /* Search */
6160 BIND_ENUM_CONSTANT(SEARCH_MATCH_CASE);
6161 BIND_ENUM_CONSTANT(SEARCH_WHOLE_WORDS);
6162 BIND_ENUM_CONSTANT(SEARCH_BACKWARDS);
6163
6164 ClassDB::bind_method(D_METHOD("set_search_text", "search_text"), &TextEdit::set_search_text);
6165 ClassDB::bind_method(D_METHOD("set_search_flags", "flags"), &TextEdit::set_search_flags);
6166
6167 ClassDB::bind_method(D_METHOD("search", "text", "flags", "from_line", "from_colum"), &TextEdit::search);
6168
6169 /* Tooltip */
6170 ClassDB::bind_method(D_METHOD("set_tooltip_request_func", "callback"), &TextEdit::set_tooltip_request_func);
6171
6172 /* Mouse */
6173 ClassDB::bind_method(D_METHOD("get_local_mouse_pos"), &TextEdit::get_local_mouse_pos);
6174
6175 ClassDB::bind_method(D_METHOD("get_word_at_pos", "position"), &TextEdit::get_word_at_pos);
6176
6177 ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position", "allow_out_of_bounds"), &TextEdit::get_line_column_at_pos, DEFVAL(true));
6178 ClassDB::bind_method(D_METHOD("get_pos_at_line_column", "line", "column"), &TextEdit::get_pos_at_line_column);
6179 ClassDB::bind_method(D_METHOD("get_rect_at_line_column", "line", "column"), &TextEdit::get_rect_at_line_column);
6180
6181 ClassDB::bind_method(D_METHOD("get_minimap_line_at_pos", "position"), &TextEdit::get_minimap_line_at_pos);
6182
6183 ClassDB::bind_method(D_METHOD("is_dragging_cursor"), &TextEdit::is_dragging_cursor);
6184 ClassDB::bind_method(D_METHOD("is_mouse_over_selection", "edges", "caret_index"), &TextEdit::is_mouse_over_selection, DEFVAL(-1));
6185
6186 /* Caret. */
6187 BIND_ENUM_CONSTANT(CARET_TYPE_LINE);
6188 BIND_ENUM_CONSTANT(CARET_TYPE_BLOCK);
6189
6190 // Internal.
6191 ClassDB::bind_method(D_METHOD("_emit_caret_changed"), &TextEdit::_emit_caret_changed);
6192
6193 ClassDB::bind_method(D_METHOD("set_caret_type", "type"), &TextEdit::set_caret_type);
6194 ClassDB::bind_method(D_METHOD("get_caret_type"), &TextEdit::get_caret_type);
6195
6196 ClassDB::bind_method(D_METHOD("set_caret_blink_enabled", "enable"), &TextEdit::set_caret_blink_enabled);
6197 ClassDB::bind_method(D_METHOD("is_caret_blink_enabled"), &TextEdit::is_caret_blink_enabled);
6198
6199 ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &TextEdit::set_caret_blink_interval);
6200 ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &TextEdit::get_caret_blink_interval);
6201
6202 ClassDB::bind_method(D_METHOD("set_draw_caret_when_editable_disabled", "enable"), &TextEdit::set_draw_caret_when_editable_disabled);
6203 ClassDB::bind_method(D_METHOD("is_drawing_caret_when_editable_disabled"), &TextEdit::is_drawing_caret_when_editable_disabled);
6204
6205 ClassDB::bind_method(D_METHOD("set_move_caret_on_right_click_enabled", "enable"), &TextEdit::set_move_caret_on_right_click_enabled);
6206 ClassDB::bind_method(D_METHOD("is_move_caret_on_right_click_enabled"), &TextEdit::is_move_caret_on_right_click_enabled);
6207
6208 ClassDB::bind_method(D_METHOD("set_caret_mid_grapheme_enabled", "enabled"), &TextEdit::set_caret_mid_grapheme_enabled);
6209 ClassDB::bind_method(D_METHOD("is_caret_mid_grapheme_enabled"), &TextEdit::is_caret_mid_grapheme_enabled);
6210
6211 ClassDB::bind_method(D_METHOD("set_multiple_carets_enabled", "enabled"), &TextEdit::set_multiple_carets_enabled);
6212 ClassDB::bind_method(D_METHOD("is_multiple_carets_enabled"), &TextEdit::is_multiple_carets_enabled);
6213
6214 ClassDB::bind_method(D_METHOD("add_caret", "line", "col"), &TextEdit::add_caret);
6215 ClassDB::bind_method(D_METHOD("remove_caret", "caret"), &TextEdit::remove_caret);
6216 ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets);
6217 ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets);
6218 ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count);
6219 ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets);
6220
6221 ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order);
6222 ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit);
6223
6224 ClassDB::bind_method(D_METHOD("is_caret_visible", "caret_index"), &TextEdit::is_caret_visible, DEFVAL(0));
6225 ClassDB::bind_method(D_METHOD("get_caret_draw_pos", "caret_index"), &TextEdit::get_caret_draw_pos, DEFVAL(0));
6226
6227 ClassDB::bind_method(D_METHOD("set_caret_line", "line", "adjust_viewport", "can_be_hidden", "wrap_index", "caret_index"), &TextEdit::set_caret_line, DEFVAL(true), DEFVAL(true), DEFVAL(0), DEFVAL(0));
6228 ClassDB::bind_method(D_METHOD("get_caret_line", "caret_index"), &TextEdit::get_caret_line, DEFVAL(0));
6229
6230 ClassDB::bind_method(D_METHOD("set_caret_column", "column", "adjust_viewport", "caret_index"), &TextEdit::set_caret_column, DEFVAL(true), DEFVAL(0));
6231 ClassDB::bind_method(D_METHOD("get_caret_column", "caret_index"), &TextEdit::get_caret_column, DEFVAL(0));
6232
6233 ClassDB::bind_method(D_METHOD("get_caret_wrap_index", "caret_index"), &TextEdit::get_caret_wrap_index, DEFVAL(0));
6234
6235 ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1));
6236
6237 /* Selection. */
6238 BIND_ENUM_CONSTANT(SELECTION_MODE_NONE);
6239 BIND_ENUM_CONSTANT(SELECTION_MODE_SHIFT);
6240 BIND_ENUM_CONSTANT(SELECTION_MODE_POINTER);
6241 BIND_ENUM_CONSTANT(SELECTION_MODE_WORD);
6242 BIND_ENUM_CONSTANT(SELECTION_MODE_LINE);
6243
6244 ClassDB::bind_method(D_METHOD("set_selecting_enabled", "enable"), &TextEdit::set_selecting_enabled);
6245 ClassDB::bind_method(D_METHOD("is_selecting_enabled"), &TextEdit::is_selecting_enabled);
6246
6247 ClassDB::bind_method(D_METHOD("set_deselect_on_focus_loss_enabled", "enable"), &TextEdit::set_deselect_on_focus_loss_enabled);
6248 ClassDB::bind_method(D_METHOD("is_deselect_on_focus_loss_enabled"), &TextEdit::is_deselect_on_focus_loss_enabled);
6249
6250 ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled);
6251 ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled);
6252
6253 ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0));
6254 ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode);
6255
6256 ClassDB::bind_method(D_METHOD("select_all"), &TextEdit::select_all);
6257 ClassDB::bind_method(D_METHOD("select_word_under_caret", "caret_index"), &TextEdit::select_word_under_caret, DEFVAL(-1));
6258 ClassDB::bind_method(D_METHOD("add_selection_for_next_occurrence"), &TextEdit::add_selection_for_next_occurrence);
6259 ClassDB::bind_method(D_METHOD("select", "from_line", "from_column", "to_line", "to_column", "caret_index"), &TextEdit::select, DEFVAL(0));
6260
6261 ClassDB::bind_method(D_METHOD("has_selection", "caret_index"), &TextEdit::has_selection, DEFVAL(-1));
6262
6263 ClassDB::bind_method(D_METHOD("get_selected_text", "caret_index"), &TextEdit::get_selected_text, DEFVAL(-1));
6264
6265 ClassDB::bind_method(D_METHOD("get_selection_line", "caret_index"), &TextEdit::get_selection_line, DEFVAL(0));
6266 ClassDB::bind_method(D_METHOD("get_selection_column", "caret_index"), &TextEdit::get_selection_column, DEFVAL(0));
6267
6268 ClassDB::bind_method(D_METHOD("get_selection_from_line", "caret_index"), &TextEdit::get_selection_from_line, DEFVAL(0));
6269 ClassDB::bind_method(D_METHOD("get_selection_from_column", "caret_index"), &TextEdit::get_selection_from_column, DEFVAL(0));
6270 ClassDB::bind_method(D_METHOD("get_selection_to_line", "caret_index"), &TextEdit::get_selection_to_line, DEFVAL(0));
6271 ClassDB::bind_method(D_METHOD("get_selection_to_column", "caret_index"), &TextEdit::get_selection_to_column, DEFVAL(0));
6272
6273 ClassDB::bind_method(D_METHOD("deselect", "caret_index"), &TextEdit::deselect, DEFVAL(-1));
6274 ClassDB::bind_method(D_METHOD("delete_selection", "caret_index"), &TextEdit::delete_selection, DEFVAL(-1));
6275
6276 /* Line wrapping. */
6277 BIND_ENUM_CONSTANT(LINE_WRAPPING_NONE);
6278 BIND_ENUM_CONSTANT(LINE_WRAPPING_BOUNDARY);
6279
6280 // Internal.
6281 ClassDB::bind_method(D_METHOD("_update_wrap_at_column", "force"), &TextEdit::_update_wrap_at_column, DEFVAL(false));
6282
6283 ClassDB::bind_method(D_METHOD("set_line_wrapping_mode", "mode"), &TextEdit::set_line_wrapping_mode);
6284 ClassDB::bind_method(D_METHOD("get_line_wrapping_mode"), &TextEdit::get_line_wrapping_mode);
6285
6286 ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &TextEdit::set_autowrap_mode);
6287 ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &TextEdit::get_autowrap_mode);
6288
6289 ClassDB::bind_method(D_METHOD("is_line_wrapped", "line"), &TextEdit::is_line_wrapped);
6290 ClassDB::bind_method(D_METHOD("get_line_wrap_count", "line"), &TextEdit::get_line_wrap_count);
6291 ClassDB::bind_method(D_METHOD("get_line_wrap_index_at_column", "line", "column"), &TextEdit::get_line_wrap_index_at_column);
6292
6293 ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_line_wrapped_text);
6294
6295 /* Viewport. */
6296 // Scrolling.
6297 ClassDB::bind_method(D_METHOD("set_smooth_scroll_enabled", "enable"), &TextEdit::set_smooth_scroll_enabled);
6298 ClassDB::bind_method(D_METHOD("is_smooth_scroll_enabled"), &TextEdit::is_smooth_scroll_enabled);
6299
6300 ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &TextEdit::get_v_scroll_bar);
6301 ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &TextEdit::get_h_scroll_bar);
6302
6303 ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &TextEdit::set_v_scroll);
6304 ClassDB::bind_method(D_METHOD("get_v_scroll"), &TextEdit::get_v_scroll);
6305
6306 ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &TextEdit::set_h_scroll);
6307 ClassDB::bind_method(D_METHOD("get_h_scroll"), &TextEdit::get_h_scroll);
6308
6309 ClassDB::bind_method(D_METHOD("set_scroll_past_end_of_file_enabled", "enable"), &TextEdit::set_scroll_past_end_of_file_enabled);
6310 ClassDB::bind_method(D_METHOD("is_scroll_past_end_of_file_enabled"), &TextEdit::is_scroll_past_end_of_file_enabled);
6311
6312 ClassDB::bind_method(D_METHOD("set_v_scroll_speed", "speed"), &TextEdit::set_v_scroll_speed);
6313 ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed);
6314
6315 ClassDB::bind_method(D_METHOD("set_fit_content_height_enabled", "enabled"), &TextEdit::set_fit_content_height_enabled);
6316 ClassDB::bind_method(D_METHOD("is_fit_content_height_enabled"), &TextEdit::is_fit_content_height_enabled);
6317
6318 ClassDB::bind_method(D_METHOD("get_scroll_pos_for_line", "line", "wrap_index"), &TextEdit::get_scroll_pos_for_line, DEFVAL(0));
6319
6320 // Visible lines.
6321 ClassDB::bind_method(D_METHOD("set_line_as_first_visible", "line", "wrap_index"), &TextEdit::set_line_as_first_visible, DEFVAL(0));
6322 ClassDB::bind_method(D_METHOD("get_first_visible_line"), &TextEdit::get_first_visible_line);
6323
6324 ClassDB::bind_method(D_METHOD("set_line_as_center_visible", "line", "wrap_index"), &TextEdit::set_line_as_center_visible, DEFVAL(0));
6325
6326 ClassDB::bind_method(D_METHOD("set_line_as_last_visible", "line", "wrap_index"), &TextEdit::set_line_as_last_visible, DEFVAL(0));
6327 ClassDB::bind_method(D_METHOD("get_last_full_visible_line"), &TextEdit::get_last_full_visible_line);
6328 ClassDB::bind_method(D_METHOD("get_last_full_visible_line_wrap_index"), &TextEdit::get_last_full_visible_line_wrap_index);
6329
6330 ClassDB::bind_method(D_METHOD("get_visible_line_count"), &TextEdit::get_visible_line_count);
6331 ClassDB::bind_method(D_METHOD("get_visible_line_count_in_range", "from_line", "to_line"), &TextEdit::get_visible_line_count_in_range);
6332 ClassDB::bind_method(D_METHOD("get_total_visible_line_count"), &TextEdit::get_total_visible_line_count);
6333
6334 // Auto adjust
6335 ClassDB::bind_method(D_METHOD("adjust_viewport_to_caret", "caret_index"), &TextEdit::adjust_viewport_to_caret, DEFVAL(0));
6336 ClassDB::bind_method(D_METHOD("center_viewport_to_caret", "caret_index"), &TextEdit::center_viewport_to_caret, DEFVAL(0));
6337
6338 // Minimap
6339 ClassDB::bind_method(D_METHOD("set_draw_minimap", "enabled"), &TextEdit::set_draw_minimap);
6340 ClassDB::bind_method(D_METHOD("is_drawing_minimap"), &TextEdit::is_drawing_minimap);
6341
6342 ClassDB::bind_method(D_METHOD("set_minimap_width", "width"), &TextEdit::set_minimap_width);
6343 ClassDB::bind_method(D_METHOD("get_minimap_width"), &TextEdit::get_minimap_width);
6344
6345 ClassDB::bind_method(D_METHOD("get_minimap_visible_lines"), &TextEdit::get_minimap_visible_lines);
6346
6347 /* Gutters. */
6348 BIND_ENUM_CONSTANT(GUTTER_TYPE_STRING);
6349 BIND_ENUM_CONSTANT(GUTTER_TYPE_ICON);
6350 BIND_ENUM_CONSTANT(GUTTER_TYPE_CUSTOM);
6351
6352 ClassDB::bind_method(D_METHOD("add_gutter", "at"), &TextEdit::add_gutter, DEFVAL(-1));
6353 ClassDB::bind_method(D_METHOD("remove_gutter", "gutter"), &TextEdit::remove_gutter);
6354 ClassDB::bind_method(D_METHOD("get_gutter_count"), &TextEdit::get_gutter_count);
6355 ClassDB::bind_method(D_METHOD("set_gutter_name", "gutter", "name"), &TextEdit::set_gutter_name);
6356 ClassDB::bind_method(D_METHOD("get_gutter_name", "gutter"), &TextEdit::get_gutter_name);
6357 ClassDB::bind_method(D_METHOD("set_gutter_type", "gutter", "type"), &TextEdit::set_gutter_type);
6358 ClassDB::bind_method(D_METHOD("get_gutter_type", "gutter"), &TextEdit::get_gutter_type);
6359 ClassDB::bind_method(D_METHOD("set_gutter_width", "gutter", "width"), &TextEdit::set_gutter_width);
6360 ClassDB::bind_method(D_METHOD("get_gutter_width", "gutter"), &TextEdit::get_gutter_width);
6361 ClassDB::bind_method(D_METHOD("set_gutter_draw", "gutter", "draw"), &TextEdit::set_gutter_draw);
6362 ClassDB::bind_method(D_METHOD("is_gutter_drawn", "gutter"), &TextEdit::is_gutter_drawn);
6363 ClassDB::bind_method(D_METHOD("set_gutter_clickable", "gutter", "clickable"), &TextEdit::set_gutter_clickable);
6364 ClassDB::bind_method(D_METHOD("is_gutter_clickable", "gutter"), &TextEdit::is_gutter_clickable);
6365 ClassDB::bind_method(D_METHOD("set_gutter_overwritable", "gutter", "overwritable"), &TextEdit::set_gutter_overwritable);
6366 ClassDB::bind_method(D_METHOD("is_gutter_overwritable", "gutter"), &TextEdit::is_gutter_overwritable);
6367 ClassDB::bind_method(D_METHOD("merge_gutters", "from_line", "to_line"), &TextEdit::merge_gutters);
6368 ClassDB::bind_method(D_METHOD("set_gutter_custom_draw", "column", "draw_callback"), &TextEdit::set_gutter_custom_draw);
6369 ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width);
6370
6371 // Line gutters.
6372 ClassDB::bind_method(D_METHOD("set_line_gutter_metadata", "line", "gutter", "metadata"), &TextEdit::set_line_gutter_metadata);
6373 ClassDB::bind_method(D_METHOD("get_line_gutter_metadata", "line", "gutter"), &TextEdit::get_line_gutter_metadata);
6374 ClassDB::bind_method(D_METHOD("set_line_gutter_text", "line", "gutter", "text"), &TextEdit::set_line_gutter_text);
6375 ClassDB::bind_method(D_METHOD("get_line_gutter_text", "line", "gutter"), &TextEdit::get_line_gutter_text);
6376 ClassDB::bind_method(D_METHOD("set_line_gutter_icon", "line", "gutter", "icon"), &TextEdit::set_line_gutter_icon);
6377 ClassDB::bind_method(D_METHOD("get_line_gutter_icon", "line", "gutter"), &TextEdit::get_line_gutter_icon);
6378 ClassDB::bind_method(D_METHOD("set_line_gutter_item_color", "line", "gutter", "color"), &TextEdit::set_line_gutter_item_color);
6379 ClassDB::bind_method(D_METHOD("get_line_gutter_item_color", "line", "gutter"), &TextEdit::get_line_gutter_item_color);
6380 ClassDB::bind_method(D_METHOD("set_line_gutter_clickable", "line", "gutter", "clickable"), &TextEdit::set_line_gutter_clickable);
6381 ClassDB::bind_method(D_METHOD("is_line_gutter_clickable", "line", "gutter"), &TextEdit::is_line_gutter_clickable);
6382
6383 // Line style
6384 ClassDB::bind_method(D_METHOD("set_line_background_color", "line", "color"), &TextEdit::set_line_background_color);
6385 ClassDB::bind_method(D_METHOD("get_line_background_color", "line"), &TextEdit::get_line_background_color);
6386
6387 /* Syntax Highlighting. */
6388 ClassDB::bind_method(D_METHOD("set_syntax_highlighter", "syntax_highlighter"), &TextEdit::set_syntax_highlighter);
6389 ClassDB::bind_method(D_METHOD("get_syntax_highlighter"), &TextEdit::get_syntax_highlighter);
6390
6391 /* Visual. */
6392 ClassDB::bind_method(D_METHOD("set_highlight_current_line", "enabled"), &TextEdit::set_highlight_current_line);
6393 ClassDB::bind_method(D_METHOD("is_highlight_current_line_enabled"), &TextEdit::is_highlight_current_line_enabled);
6394
6395 ClassDB::bind_method(D_METHOD("set_highlight_all_occurrences", "enabled"), &TextEdit::set_highlight_all_occurrences);
6396 ClassDB::bind_method(D_METHOD("is_highlight_all_occurrences_enabled"), &TextEdit::is_highlight_all_occurrences_enabled);
6397
6398 ClassDB::bind_method(D_METHOD("get_draw_control_chars"), &TextEdit::get_draw_control_chars);
6399 ClassDB::bind_method(D_METHOD("set_draw_control_chars", "enabled"), &TextEdit::set_draw_control_chars);
6400
6401 ClassDB::bind_method(D_METHOD("set_draw_tabs", "enabled"), &TextEdit::set_draw_tabs);
6402 ClassDB::bind_method(D_METHOD("is_drawing_tabs"), &TextEdit::is_drawing_tabs);
6403
6404 ClassDB::bind_method(D_METHOD("set_draw_spaces", "enabled"), &TextEdit::set_draw_spaces);
6405 ClassDB::bind_method(D_METHOD("is_drawing_spaces"), &TextEdit::is_drawing_spaces);
6406
6407 ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu);
6408 ClassDB::bind_method(D_METHOD("is_menu_visible"), &TextEdit::is_menu_visible);
6409 ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
6410
6411 /* Inspector */
6412 ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
6413 ADD_PROPERTY(PropertyInfo(Variant::STRING, "placeholder_text", PROPERTY_HINT_MULTILINE_TEXT), "set_placeholder", "get_placeholder");
6414
6415 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
6416 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");
6417 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_keys_enabled"), "set_shortcut_keys_enabled", "is_shortcut_keys_enabled");
6418 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selecting_enabled"), "set_selecting_enabled", "is_selecting_enabled");
6419 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled");
6420 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_and_drop_selection_enabled"), "set_drag_and_drop_selection_enabled", "is_drag_and_drop_selection_enabled");
6421 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "virtual_keyboard_enabled"), "set_virtual_keyboard_enabled", "is_virtual_keyboard_enabled");
6422 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled");
6423 ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode");
6424 ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Arbitrary:1,Word:2,Word (Smart):3"), "set_autowrap_mode", "get_autowrap_mode");
6425
6426 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
6427 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
6428 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars");
6429 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_tabs"), "set_draw_tabs", "is_drawing_tabs");
6430 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_spaces"), "set_draw_spaces", "is_drawing_spaces");
6431
6432 ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
6433
6434 ADD_GROUP("Scroll", "scroll_");
6435 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_smooth"), "set_smooth_scroll_enabled", "is_smooth_scroll_enabled");
6436 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_v_scroll_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_v_scroll_speed", "get_v_scroll_speed");
6437 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_past_end_of_file"), "set_scroll_past_end_of_file_enabled", "is_scroll_past_end_of_file_enabled");
6438 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll");
6439 ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll");
6440 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scroll_fit_content_height"), "set_fit_content_height_enabled", "is_fit_content_height_enabled");
6441
6442 ADD_GROUP("Minimap", "minimap_");
6443 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_draw"), "set_draw_minimap", "is_drawing_minimap");
6444 ADD_PROPERTY(PropertyInfo(Variant::INT, "minimap_width", PROPERTY_HINT_NONE, "suffix:px"), "set_minimap_width", "get_minimap_width");
6445
6446 ADD_GROUP("Caret", "caret_");
6447 ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_type", PROPERTY_HINT_ENUM, "Line,Block"), "set_caret_type", "get_caret_type");
6448 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled");
6449 ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval");
6450 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_draw_when_editable_disabled"), "set_draw_caret_when_editable_disabled", "is_drawing_caret_when_editable_disabled");
6451 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled");
6452 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
6453 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
6454
6455 ADD_GROUP("BiDi", "");
6456 ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
6457 ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
6458 ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
6459 ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
6460
6461 /* Signals */
6462 /* Core. */
6463 ADD_SIGNAL(MethodInfo("text_set"));
6464 ADD_SIGNAL(MethodInfo("text_changed"));
6465 ADD_SIGNAL(MethodInfo("lines_edited_from", PropertyInfo(Variant::INT, "from_line"), PropertyInfo(Variant::INT, "to_line")));
6466
6467 /* Caret. */
6468 ADD_SIGNAL(MethodInfo("caret_changed"));
6469
6470 /* Gutters. */
6471 ADD_SIGNAL(MethodInfo("gutter_clicked", PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "gutter")));
6472 ADD_SIGNAL(MethodInfo("gutter_added"));
6473 ADD_SIGNAL(MethodInfo("gutter_removed"));
6474
6475 /* Theme items */
6476 /* Search */
6477 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_color);
6478 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, search_result_border_color);
6479
6480 /* Caret */
6481 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TextEdit, caret_width);
6482 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, caret_color);
6483 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, caret_background_color);
6484
6485 /* Selection */
6486 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, font_selected_color);
6487 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, selection_color);
6488
6489 /* Other visuals */
6490 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TextEdit, style_normal, "normal");
6491 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TextEdit, style_focus, "focus");
6492 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TextEdit, style_readonly, "read_only");
6493
6494 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TextEdit, tab_icon, "tab");
6495 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TextEdit, space_icon, "space");
6496
6497 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, TextEdit, font);
6498 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, TextEdit, font_size);
6499 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, font_color);
6500 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, font_readonly_color);
6501 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, font_placeholder_color);
6502
6503 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TextEdit, outline_size);
6504 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, TextEdit, outline_color, "font_outline_color");
6505
6506 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, TextEdit, line_spacing);
6507
6508 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, background_color);
6509 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, current_line_color);
6510 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, TextEdit, word_highlighted_color);
6511
6512 /* Settings. */
6513 GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/text_edit_idle_detect_sec", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 3);
6514 GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/common/text_edit_undo_stack_max_size", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 1024);
6515}
6516
6517/* Internal API for CodeEdit. */
6518// Line hiding.
6519void TextEdit::_set_hiding_enabled(bool p_enabled) {
6520 if (hiding_enabled == p_enabled) {
6521 return;
6522 }
6523
6524 if (!p_enabled) {
6525 _unhide_all_lines();
6526 }
6527 hiding_enabled = p_enabled;
6528 queue_redraw();
6529}
6530
6531bool TextEdit::_is_hiding_enabled() const {
6532 return hiding_enabled;
6533}
6534
6535bool TextEdit::_is_line_hidden(int p_line) const {
6536 ERR_FAIL_INDEX_V(p_line, text.size(), false);
6537 return text.is_hidden(p_line);
6538}
6539
6540void TextEdit::_unhide_all_lines() {
6541 for (int i = 0; i < text.size(); i++) {
6542 text.set_hidden(i, false);
6543 }
6544 _update_scrollbars();
6545 queue_redraw();
6546}
6547
6548void TextEdit::_set_line_as_hidden(int p_line, bool p_hidden) {
6549 ERR_FAIL_INDEX(p_line, text.size());
6550
6551 if (text.is_hidden(p_line) == p_hidden) {
6552 return;
6553 }
6554
6555 if (_is_hiding_enabled() || !p_hidden) {
6556 text.set_hidden(p_line, p_hidden);
6557 }
6558 queue_redraw();
6559}
6560
6561// Symbol lookup.
6562void TextEdit::_set_symbol_lookup_word(const String &p_symbol) {
6563 if (lookup_symbol_word == p_symbol) {
6564 return;
6565 }
6566
6567 lookup_symbol_word = p_symbol;
6568 queue_redraw();
6569}
6570
6571/* Text manipulation */
6572
6573// Overridable actions
6574void TextEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
6575 ERR_FAIL_COND(p_caret > carets.size());
6576 if (!editable) {
6577 return;
6578 }
6579
6580 start_action(EditAction::ACTION_TYPING);
6581 Vector<int> caret_edit_order = get_caret_index_edit_order();
6582 for (const int &i : caret_edit_order) {
6583 if (p_caret != -1 && p_caret != i) {
6584 continue;
6585 }
6586
6587 // Remove the old character if in insert mode and no selection.
6588 if (overtype_mode && !has_selection(i)) {
6589 // Make sure we don't try and remove empty space.
6590 int cl = get_caret_line(i);
6591 int cc = get_caret_column(i);
6592 if (cc < get_line(cl).length()) {
6593 _remove_text(cl, cc, cl, cc + 1);
6594 }
6595 }
6596
6597 const char32_t chr[2] = { (char32_t)p_unicode, 0 };
6598 insert_text_at_caret(chr, i);
6599 }
6600 end_action();
6601}
6602
6603void TextEdit::_backspace_internal(int p_caret) {
6604 ERR_FAIL_COND(p_caret > carets.size());
6605 if (!editable) {
6606 return;
6607 }
6608
6609 if (has_selection(p_caret)) {
6610 delete_selection(p_caret);
6611 return;
6612 }
6613
6614 begin_complex_operation();
6615 Vector<int> caret_edit_order = get_caret_index_edit_order();
6616 for (const int &i : caret_edit_order) {
6617 if (p_caret != -1 && p_caret != i) {
6618 continue;
6619 }
6620
6621 int cc = get_caret_column(i);
6622 int cl = get_caret_line(i);
6623
6624 if (cc == 0 && cl == 0) {
6625 continue;
6626 }
6627
6628 int prev_line = cc ? cl : cl - 1;
6629 int prev_column = cc ? (cc - 1) : (text[cl - 1].length());
6630
6631 merge_gutters(prev_line, cl);
6632
6633 if (_is_line_hidden(cl)) {
6634 _set_line_as_hidden(prev_line, true);
6635 }
6636 _remove_text(prev_line, prev_column, cl, cc);
6637
6638 set_caret_line(prev_line, false, true, 0, i);
6639 set_caret_column(prev_column, i == 0, i);
6640
6641 adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
6642 }
6643 merge_overlapping_carets();
6644 end_complex_operation();
6645}
6646
6647void TextEdit::_cut_internal(int p_caret) {
6648 ERR_FAIL_COND(p_caret > carets.size());
6649 if (!editable) {
6650 return;
6651 }
6652
6653 if (has_selection(p_caret)) {
6654 DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
6655 delete_selection(p_caret);
6656 cut_copy_line = "";
6657 return;
6658 }
6659
6660 begin_complex_operation();
6661 Vector<int> carets_to_remove;
6662
6663 StringBuilder clipboard;
6664 // This is the exception and has to edit in reverse order else the string copied to the clipboard will be backwards.
6665 Vector<int> caret_edit_order = get_caret_index_edit_order();
6666 for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
6667 int caret_idx = caret_edit_order[i];
6668 if (p_caret != -1 && p_caret != caret_idx) {
6669 continue;
6670 }
6671
6672 int cl = get_caret_line(caret_idx);
6673 int cc = get_caret_column(caret_idx);
6674 int indent_level = get_indent_level(cl);
6675 double hscroll = get_h_scroll();
6676
6677 // Check for overlapping carets.
6678 // We don't need to worry about selections as that is caught before this entire section.
6679 for (int j = i - 1; j >= 0; j--) {
6680 if (get_caret_line(caret_edit_order[j]) == cl) {
6681 carets_to_remove.push_back(caret_edit_order[j]);
6682 i = j;
6683 }
6684 }
6685
6686 clipboard += text[cl];
6687 if (p_caret == -1 && caret_idx != 0) {
6688 clipboard += "\n";
6689 }
6690
6691 if (cl == 0 && get_line_count() > 1) {
6692 _remove_text(cl, 0, cl + 1, 0);
6693 adjust_carets_after_edit(caret_idx, cl, 0, cl + 1, text[cl].length());
6694 } else {
6695 _remove_text(cl, 0, cl, text[cl].length());
6696 set_caret_column(0, false, caret_idx);
6697 backspace(caret_idx);
6698 set_caret_line(get_caret_line(caret_idx) + 1, caret_idx == 0, 0, 0, caret_idx);
6699 }
6700
6701 // Correct the visually perceived caret column taking care of indentation level of the lines.
6702 int diff_indent = indent_level - get_indent_level(get_caret_line(caret_idx));
6703 cc += diff_indent;
6704 if (diff_indent != 0) {
6705 cc += diff_indent > 0 ? -1 : 1;
6706 }
6707
6708 // Restore horizontal scroll and caret column modified by the backspace() call.
6709 set_h_scroll(hscroll);
6710 set_caret_column(cc, caret_idx == 0, caret_idx);
6711 }
6712
6713 // Sort and remove backwards to preserve indexes.
6714 carets_to_remove.sort();
6715 for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
6716 remove_caret(carets_to_remove[i]);
6717 }
6718 end_complex_operation();
6719
6720 String clipboard_string = clipboard.as_string();
6721 DisplayServer::get_singleton()->clipboard_set(clipboard_string);
6722 cut_copy_line = clipboard_string;
6723}
6724
6725void TextEdit::_copy_internal(int p_caret) {
6726 ERR_FAIL_COND(p_caret > carets.size());
6727 if (has_selection(p_caret)) {
6728 DisplayServer::get_singleton()->clipboard_set(get_selected_text(p_caret));
6729 cut_copy_line = "";
6730 return;
6731 }
6732
6733 StringBuilder clipboard;
6734 Vector<int> caret_edit_order = get_caret_index_edit_order();
6735 for (int i = caret_edit_order.size() - 1; i >= 0; i--) {
6736 int caret_idx = caret_edit_order[i];
6737 if (p_caret != -1 && p_caret != caret_idx) {
6738 continue;
6739 }
6740
6741 int cl = get_caret_line(caret_idx);
6742 if (text[cl].length() != 0) {
6743 clipboard += _base_get_text(cl, 0, cl, text[cl].length());
6744 if (p_caret == -1 && i != 0) {
6745 clipboard += "\n";
6746 }
6747 }
6748 }
6749
6750 String clipboard_string = clipboard.as_string();
6751 DisplayServer::get_singleton()->clipboard_set(clipboard_string);
6752 cut_copy_line = clipboard_string;
6753}
6754
6755void TextEdit::_paste_internal(int p_caret) {
6756 ERR_FAIL_COND(p_caret > carets.size());
6757 if (!editable) {
6758 return;
6759 }
6760
6761 String clipboard = DisplayServer::get_singleton()->clipboard_get();
6762 Vector<String> clipboad_lines = clipboard.split("\n");
6763 bool insert_line_per_caret = p_caret == -1 && carets.size() > 1 && clipboad_lines.size() == carets.size();
6764
6765 begin_complex_operation();
6766 Vector<int> caret_edit_order = get_caret_index_edit_order();
6767 int clipboad_line = clipboad_lines.size() - 1;
6768 for (const int &i : caret_edit_order) {
6769 if (p_caret != -1 && p_caret != i) {
6770 continue;
6771 }
6772
6773 if (has_selection(i)) {
6774 delete_selection(i);
6775 } else if (!cut_copy_line.is_empty() && cut_copy_line == clipboard) {
6776 set_caret_column(0, i == 0, i);
6777 String ins = "\n";
6778 clipboard += ins;
6779 }
6780
6781 if (insert_line_per_caret) {
6782 clipboard = clipboad_lines[clipboad_line];
6783 }
6784
6785 insert_text_at_caret(clipboard, i);
6786 clipboad_line--;
6787 }
6788 end_complex_operation();
6789}
6790
6791void TextEdit::_paste_primary_clipboard_internal(int p_caret) {
6792 ERR_FAIL_COND(p_caret > carets.size());
6793 if (!is_editable() || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
6794 return;
6795 }
6796
6797 String paste_buffer = DisplayServer::get_singleton()->clipboard_get_primary();
6798
6799 if (carets.size() == 1) {
6800 Point2i pos = get_line_column_at_pos(get_local_mouse_pos());
6801 deselect();
6802 set_caret_line(pos.y, true, false);
6803 set_caret_column(pos.x);
6804 }
6805
6806 if (!paste_buffer.is_empty()) {
6807 insert_text_at_caret(paste_buffer);
6808 }
6809
6810 grab_focus();
6811}
6812
6813// Context menu.
6814
6815Key TextEdit::_get_menu_action_accelerator(const String &p_action) {
6816 const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(p_action);
6817 if (!events) {
6818 return Key::NONE;
6819 }
6820
6821 // Use first event in the list for the accelerator.
6822 const List<Ref<InputEvent>>::Element *first_event = events->front();
6823 if (!first_event) {
6824 return Key::NONE;
6825 }
6826
6827 const Ref<InputEventKey> event = first_event->get();
6828 if (event.is_null()) {
6829 return Key::NONE;
6830 }
6831
6832 // Use physical keycode if non-zero.
6833 if (event->get_physical_keycode() != Key::NONE) {
6834 return event->get_physical_keycode_with_modifiers();
6835 } else {
6836 return event->get_keycode_with_modifiers();
6837 }
6838}
6839
6840void TextEdit::_generate_context_menu() {
6841 menu = memnew(PopupMenu);
6842 add_child(menu, false, INTERNAL_MODE_FRONT);
6843
6844 menu_dir = memnew(PopupMenu);
6845 menu_dir->set_name("DirMenu");
6846 menu_dir->add_radio_check_item(RTR("Same as Layout Direction"), MENU_DIR_INHERITED);
6847 menu_dir->add_radio_check_item(RTR("Auto-Detect Direction"), MENU_DIR_AUTO);
6848 menu_dir->add_radio_check_item(RTR("Left-to-Right"), MENU_DIR_LTR);
6849 menu_dir->add_radio_check_item(RTR("Right-to-Left"), MENU_DIR_RTL);
6850 menu->add_child(menu_dir, false, INTERNAL_MODE_FRONT);
6851
6852 menu_ctl = memnew(PopupMenu);
6853 menu_ctl->set_name("CTLMenu");
6854 menu_ctl->add_item(RTR("Left-to-Right Mark (LRM)"), MENU_INSERT_LRM);
6855 menu_ctl->add_item(RTR("Right-to-Left Mark (RLM)"), MENU_INSERT_RLM);
6856 menu_ctl->add_item(RTR("Start of Left-to-Right Embedding (LRE)"), MENU_INSERT_LRE);
6857 menu_ctl->add_item(RTR("Start of Right-to-Left Embedding (RLE)"), MENU_INSERT_RLE);
6858 menu_ctl->add_item(RTR("Start of Left-to-Right Override (LRO)"), MENU_INSERT_LRO);
6859 menu_ctl->add_item(RTR("Start of Right-to-Left Override (RLO)"), MENU_INSERT_RLO);
6860 menu_ctl->add_item(RTR("Pop Direction Formatting (PDF)"), MENU_INSERT_PDF);
6861 menu_ctl->add_separator();
6862 menu_ctl->add_item(RTR("Arabic Letter Mark (ALM)"), MENU_INSERT_ALM);
6863 menu_ctl->add_item(RTR("Left-to-Right Isolate (LRI)"), MENU_INSERT_LRI);
6864 menu_ctl->add_item(RTR("Right-to-Left Isolate (RLI)"), MENU_INSERT_RLI);
6865 menu_ctl->add_item(RTR("First Strong Isolate (FSI)"), MENU_INSERT_FSI);
6866 menu_ctl->add_item(RTR("Pop Direction Isolate (PDI)"), MENU_INSERT_PDI);
6867 menu_ctl->add_separator();
6868 menu_ctl->add_item(RTR("Zero-Width Joiner (ZWJ)"), MENU_INSERT_ZWJ);
6869 menu_ctl->add_item(RTR("Zero-Width Non-Joiner (ZWNJ)"), MENU_INSERT_ZWNJ);
6870 menu_ctl->add_item(RTR("Word Joiner (WJ)"), MENU_INSERT_WJ);
6871 menu_ctl->add_item(RTR("Soft Hyphen (SHY)"), MENU_INSERT_SHY);
6872 menu->add_child(menu_ctl, false, INTERNAL_MODE_FRONT);
6873
6874 menu->add_item(RTR("Cut"), MENU_CUT);
6875 menu->add_item(RTR("Copy"), MENU_COPY);
6876 menu->add_item(RTR("Paste"), MENU_PASTE);
6877 menu->add_separator();
6878 menu->add_item(RTR("Select All"), MENU_SELECT_ALL);
6879 menu->add_item(RTR("Clear"), MENU_CLEAR);
6880 menu->add_separator();
6881 menu->add_item(RTR("Undo"), MENU_UNDO);
6882 menu->add_item(RTR("Redo"), MENU_REDO);
6883 menu->add_separator();
6884 menu->add_submenu_item(RTR("Text Writing Direction"), "DirMenu", MENU_SUBMENU_TEXT_DIR);
6885 menu->add_separator();
6886 menu->add_check_item(RTR("Display Control Characters"), MENU_DISPLAY_UCC);
6887 menu->add_submenu_item(RTR("Insert Control Character"), "CTLMenu", MENU_SUBMENU_INSERT_UCC);
6888
6889 menu->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
6890 menu_dir->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
6891 menu_ctl->connect("id_pressed", callable_mp(this, &TextEdit::menu_option));
6892}
6893
6894void TextEdit::_update_context_menu() {
6895 if (!menu) {
6896 _generate_context_menu();
6897 }
6898
6899 int idx = -1;
6900
6901#define MENU_ITEM_ACTION_DISABLED(m_menu, m_id, m_action, m_disabled) \
6902 idx = m_menu->get_item_index(m_id); \
6903 if (idx >= 0) { \
6904 m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
6905 m_menu->set_item_disabled(idx, m_disabled); \
6906 }
6907
6908#define MENU_ITEM_ACTION(m_menu, m_id, m_action) \
6909 idx = m_menu->get_item_index(m_id); \
6910 if (idx >= 0) { \
6911 m_menu->set_item_accelerator(idx, shortcut_keys_enabled ? _get_menu_action_accelerator(m_action) : Key::NONE); \
6912 }
6913
6914#define MENU_ITEM_DISABLED(m_menu, m_id, m_disabled) \
6915 idx = m_menu->get_item_index(m_id); \
6916 if (idx >= 0) { \
6917 m_menu->set_item_disabled(idx, m_disabled); \
6918 }
6919
6920#define MENU_ITEM_CHECKED(m_menu, m_id, m_checked) \
6921 idx = m_menu->get_item_index(m_id); \
6922 if (idx >= 0) { \
6923 m_menu->set_item_checked(idx, m_checked); \
6924 }
6925
6926 MENU_ITEM_ACTION_DISABLED(menu, MENU_CUT, "ui_cut", !editable)
6927 MENU_ITEM_ACTION(menu, MENU_COPY, "ui_copy")
6928 MENU_ITEM_ACTION_DISABLED(menu, MENU_PASTE, "ui_paste", !editable)
6929 MENU_ITEM_ACTION_DISABLED(menu, MENU_SELECT_ALL, "ui_text_select_all", !selecting_enabled)
6930 MENU_ITEM_DISABLED(menu, MENU_CLEAR, !editable)
6931 MENU_ITEM_ACTION_DISABLED(menu, MENU_UNDO, "ui_undo", !editable || !has_undo())
6932 MENU_ITEM_ACTION_DISABLED(menu, MENU_REDO, "ui_redo", !editable || !has_redo())
6933 MENU_ITEM_CHECKED(menu_dir, MENU_DIR_INHERITED, text_direction == TEXT_DIRECTION_INHERITED)
6934 MENU_ITEM_CHECKED(menu_dir, MENU_DIR_AUTO, text_direction == TEXT_DIRECTION_AUTO)
6935 MENU_ITEM_CHECKED(menu_dir, MENU_DIR_LTR, text_direction == TEXT_DIRECTION_LTR)
6936 MENU_ITEM_CHECKED(menu_dir, MENU_DIR_RTL, text_direction == TEXT_DIRECTION_RTL)
6937 MENU_ITEM_CHECKED(menu, MENU_DISPLAY_UCC, draw_control_chars)
6938 MENU_ITEM_DISABLED(menu, MENU_SUBMENU_INSERT_UCC, !editable)
6939
6940#undef MENU_ITEM_ACTION_DISABLED
6941#undef MENU_ITEM_ACTION
6942#undef MENU_ITEM_DISABLED
6943#undef MENU_ITEM_CHECKED
6944}
6945
6946/* Versioning */
6947void TextEdit::_push_current_op() {
6948 if (pending_action_end) {
6949 start_action(EditAction::ACTION_NONE);
6950 return;
6951 }
6952 if (current_op.type == TextOperation::TYPE_NONE) {
6953 return; // Nothing to do.
6954 }
6955
6956 if (next_operation_is_complex) {
6957 current_op.chain_forward = true;
6958 next_operation_is_complex = false;
6959 }
6960
6961 undo_stack.push_back(current_op);
6962 current_op.type = TextOperation::TYPE_NONE;
6963 current_op.text = "";
6964 current_op.chain_forward = false;
6965
6966 if (undo_stack.size() > undo_stack_max_size) {
6967 undo_stack.pop_front();
6968 }
6969}
6970
6971void TextEdit::_do_text_op(const TextOperation &p_op, bool p_reverse) {
6972 ERR_FAIL_COND(p_op.type == TextOperation::TYPE_NONE);
6973
6974 bool insert = p_op.type == TextOperation::TYPE_INSERT;
6975 if (p_reverse) {
6976 insert = !insert;
6977 }
6978
6979 if (insert) {
6980 int check_line;
6981 int check_column;
6982 _base_insert_text(p_op.from_line, p_op.from_column, p_op.text, check_line, check_column);
6983 ERR_FAIL_COND(check_line != p_op.to_line); // BUG.
6984 ERR_FAIL_COND(check_column != p_op.to_column); // BUG.
6985 } else {
6986 _base_remove_text(p_op.from_line, p_op.from_column, p_op.to_line, p_op.to_column);
6987 }
6988}
6989
6990void TextEdit::_clear_redo() {
6991 if (undo_stack_pos == nullptr) {
6992 return; // Nothing to clear.
6993 }
6994
6995 _push_current_op();
6996
6997 while (undo_stack_pos) {
6998 List<TextOperation>::Element *elem = undo_stack_pos;
6999 undo_stack_pos = undo_stack_pos->next();
7000 undo_stack.erase(elem);
7001 }
7002}
7003
7004/* Search */
7005int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_search, uint32_t p_search_flags, int p_from_column) const {
7006 int col = -1;
7007
7008 if (p_key.length() > 0 && p_search.length() > 0) {
7009 if (p_from_column < 0 || p_from_column > p_search.length()) {
7010 p_from_column = 0;
7011 }
7012
7013 while (col == -1 && p_from_column <= p_search.length()) {
7014 if (p_search_flags & SEARCH_MATCH_CASE) {
7015 col = p_search.find(p_key, p_from_column);
7016 } else {
7017 col = p_search.findn(p_key, p_from_column);
7018 }
7019
7020 // If not found, just break early to improve performance.
7021 if (col == -1) {
7022 break;
7023 }
7024
7025 // Whole words only.
7026 if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) {
7027 p_from_column = col;
7028
7029 if (col > 0 && !is_symbol(p_search[col - 1])) {
7030 col = -1;
7031 } else if ((col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) {
7032 col = -1;
7033 }
7034 }
7035
7036 p_from_column += 1;
7037 }
7038 }
7039 return col;
7040}
7041
7042/* Mouse */
7043int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const {
7044 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
7045 p_wrap_index = MIN(p_wrap_index, text.get_line_data(p_line)->get_line_count() - 1);
7046
7047 RID text_rid = text.get_line_data(p_line)->get_line_rid(p_wrap_index);
7048 if (is_layout_rtl()) {
7049 p_px = TS->shaped_text_get_size(text_rid).x - p_px;
7050 }
7051 int ofs = TS->shaped_text_hit_test_position(text_rid, p_px);
7052 if (!caret_mid_grapheme_enabled) {
7053 ofs = TS->shaped_text_closest_character_pos(text_rid, ofs);
7054 }
7055 return ofs;
7056}
7057
7058/* Caret */
7059void TextEdit::_emit_caret_changed() {
7060 emit_signal(SNAME("caret_changed"));
7061 caret_pos_dirty = false;
7062 caret_index_edit_dirty = true;
7063}
7064
7065void TextEdit::_reset_caret_blink_timer() {
7066 if (!caret_blink_enabled) {
7067 return;
7068 }
7069
7070 draw_caret = true;
7071 if (has_focus()) {
7072 caret_blink_timer->stop();
7073 caret_blink_timer->start();
7074 queue_redraw();
7075 }
7076}
7077
7078void TextEdit::_toggle_draw_caret() {
7079 draw_caret = !draw_caret;
7080 if (is_visible_in_tree() && has_focus() && window_has_focus) {
7081 queue_redraw();
7082 }
7083}
7084
7085int TextEdit::_get_column_x_offset_for_line(int p_char, int p_line, int p_column) const {
7086 ERR_FAIL_INDEX_V(p_line, text.size(), 0);
7087
7088 int row = 0;
7089 Vector<Vector2i> rows2 = text.get_line_wrap_ranges(p_line);
7090 for (int i = 0; i < rows2.size(); i++) {
7091 if ((p_char >= rows2[i].x) && (p_char <= rows2[i].y)) {
7092 row = i;
7093 break;
7094 }
7095 }
7096
7097 RID text_rid = text.get_line_data(p_line)->get_line_rid(row);
7098 CaretInfo ts_caret = TS->shaped_text_get_carets(text_rid, p_column);
7099 if ((ts_caret.l_caret != Rect2() && (ts_caret.l_dir == TextServer::DIRECTION_AUTO || ts_caret.l_dir == (TextServer::Direction)input_direction)) || (ts_caret.t_caret == Rect2())) {
7100 return ts_caret.l_caret.position.x;
7101 } else {
7102 return ts_caret.t_caret.position.x;
7103 }
7104}
7105
7106/* Selection */
7107void TextEdit::_click_selection_held() {
7108 // Warning: is_mouse_button_pressed(MouseButton::LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
7109 // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem.
7110 // I'm unsure if there's an actual fix that doesn't have a ton of side effects.
7111 if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT) && get_selection_mode() != SelectionMode::SELECTION_MODE_NONE) {
7112 switch (get_selection_mode()) {
7113 case SelectionMode::SELECTION_MODE_POINTER: {
7114 _update_selection_mode_pointer();
7115 } break;
7116 case SelectionMode::SELECTION_MODE_WORD: {
7117 _update_selection_mode_word();
7118 } break;
7119 case SelectionMode::SELECTION_MODE_LINE: {
7120 _update_selection_mode_line();
7121 } break;
7122 default: {
7123 break;
7124 }
7125 }
7126 } else {
7127 click_select_held->stop();
7128 }
7129}
7130
7131void TextEdit::_update_selection_mode_pointer() {
7132 dragging_selection = true;
7133 Point2 mp = get_local_mouse_pos();
7134
7135 Point2i pos = get_line_column_at_pos(mp);
7136 int line = pos.y;
7137 int col = pos.x;
7138 int caret_idx = carets.size() - 1;
7139
7140 select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
7141
7142 set_caret_line(line, false, true, 0, caret_idx);
7143 set_caret_column(col, true, caret_idx);
7144 queue_redraw();
7145
7146 click_select_held->start();
7147 merge_overlapping_carets();
7148}
7149
7150void TextEdit::_update_selection_mode_word() {
7151 dragging_selection = true;
7152 Point2 mp = get_local_mouse_pos();
7153
7154 Point2i pos = get_line_column_at_pos(mp);
7155 int line = pos.y;
7156 int col = pos.x;
7157 int caret_idx = carets.size() - 1;
7158
7159 int caret_pos = CLAMP(col, 0, text[line].length());
7160 int beg = caret_pos;
7161 int end = beg;
7162 PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid());
7163 for (int i = 0; i < words.size(); i = i + 2) {
7164 if ((words[i] < caret_pos && words[i + 1] > caret_pos) || (i == words.size() - 2 && caret_pos == words[i + 1])) {
7165 beg = words[i];
7166 end = words[i + 1];
7167 break;
7168 }
7169 }
7170
7171 /* Initial selection. */
7172 if (!has_selection(caret_idx)) {
7173 select(line, beg, line, end, caret_idx);
7174 carets.write[caret_idx].selection.selecting_column = beg;
7175 carets.write[caret_idx].selection.selected_word_beg = beg;
7176 carets.write[caret_idx].selection.selected_word_end = end;
7177 carets.write[caret_idx].selection.selected_word_origin = beg;
7178 set_caret_line(line, false, true, 0, caret_idx);
7179 set_caret_column(end, true, caret_idx);
7180 } else {
7181 if ((col <= carets[caret_idx].selection.selected_word_origin && line == get_selection_line(caret_idx)) || line < get_selection_line(caret_idx)) {
7182 carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_end;
7183 select(line, beg, get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_end, caret_idx);
7184 set_caret_line(line, false, true, 0, caret_idx);
7185 set_caret_column(beg, true, caret_idx);
7186 } else {
7187 carets.write[caret_idx].selection.selecting_column = carets[caret_idx].selection.selected_word_beg;
7188 select(get_selection_line(caret_idx), carets[caret_idx].selection.selected_word_beg, line, end, caret_idx);
7189 set_caret_line(get_selection_to_line(caret_idx), false, true, 0, caret_idx);
7190 set_caret_column(get_selection_to_column(caret_idx), true, caret_idx);
7191 }
7192 }
7193
7194 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
7195 DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
7196 }
7197
7198 queue_redraw();
7199
7200 click_select_held->start();
7201 merge_overlapping_carets();
7202}
7203
7204void TextEdit::_update_selection_mode_line() {
7205 dragging_selection = true;
7206 Point2 mp = get_local_mouse_pos();
7207
7208 Point2i pos = get_line_column_at_pos(mp);
7209 int line = pos.y;
7210 int col = pos.x;
7211 int caret_idx = carets.size() - 1;
7212
7213 col = 0;
7214 if (line < carets[caret_idx].selection.selecting_line) {
7215 // Caret is above us.
7216 set_caret_line(line - 1, false, true, 0, caret_idx);
7217 carets.write[caret_idx].selection.selecting_column = has_selection(caret_idx)
7218 ? text[get_selection_line(caret_idx)].length()
7219 : 0;
7220 } else {
7221 // Caret is below us.
7222 set_caret_line(line + 1, false, true, 0, caret_idx);
7223 carets.write[caret_idx].selection.selecting_column = 0;
7224 col = text[line].length();
7225 }
7226 set_caret_column(0, false, caret_idx);
7227
7228 select(carets[caret_idx].selection.selecting_line, carets[caret_idx].selection.selecting_column, line, col, caret_idx);
7229 if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_CLIPBOARD_PRIMARY)) {
7230 DisplayServer::get_singleton()->clipboard_set_primary(get_selected_text());
7231 }
7232
7233 queue_redraw();
7234
7235 click_select_held->start();
7236 merge_overlapping_carets();
7237}
7238
7239void TextEdit::_pre_shift_selection(int p_caret) {
7240 if (!selecting_enabled) {
7241 return;
7242 }
7243
7244 if (!has_selection(p_caret) || get_selection_mode() == SelectionMode::SELECTION_MODE_NONE) {
7245 carets.write[p_caret].selection.active = true;
7246 set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
7247 return;
7248 }
7249
7250 set_selection_mode(SelectionMode::SELECTION_MODE_SHIFT, get_selection_line(p_caret), get_selection_column(p_caret), p_caret);
7251}
7252
7253void TextEdit::_post_shift_selection(int p_caret) {
7254 if (!selecting_enabled) {
7255 return;
7256 }
7257
7258 if (has_selection(p_caret) && get_selection_mode() == SelectionMode::SELECTION_MODE_SHIFT) {
7259 select(get_selection_line(p_caret), get_selection_column(p_caret), get_caret_line(p_caret), get_caret_column(p_caret), p_caret);
7260 }
7261}
7262
7263/* Line Wrapping */
7264void TextEdit::_update_wrap_at_column(bool p_force) {
7265 int new_wrap_at = get_size().width - theme_cache.style_normal->get_minimum_size().width - gutters_width - gutter_padding;
7266 if (draw_minimap) {
7267 new_wrap_at -= minimap_width;
7268 }
7269 if (v_scroll->is_visible_in_tree()) {
7270 new_wrap_at -= v_scroll->get_combined_minimum_size().width;
7271 }
7272 /* Give it a little more space. */
7273 new_wrap_at -= wrap_right_offset;
7274
7275 if ((wrap_at_column != new_wrap_at) || p_force) {
7276 wrap_at_column = new_wrap_at;
7277 if (line_wrapping_mode) {
7278 BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY;
7279 switch (autowrap_mode) {
7280 case TextServer::AUTOWRAP_WORD_SMART:
7281 autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY;
7282 break;
7283 case TextServer::AUTOWRAP_WORD:
7284 autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
7285 break;
7286 case TextServer::AUTOWRAP_ARBITRARY:
7287 autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
7288 break;
7289 case TextServer::AUTOWRAP_OFF:
7290 break;
7291 }
7292 text.set_brk_flags(autowrap_flags);
7293 text.set_width(wrap_at_column);
7294 } else {
7295 text.set_width(-1);
7296 }
7297
7298 text.invalidate_all_lines();
7299 _update_placeholder();
7300 }
7301
7302 // Update viewport.
7303 int first_vis_line = get_first_visible_line();
7304 if (is_line_wrapped(first_vis_line)) {
7305 first_visible_line_wrap_ofs = MIN(first_visible_line_wrap_ofs, get_line_wrap_count(first_vis_line));
7306 } else {
7307 first_visible_line_wrap_ofs = 0;
7308 }
7309 set_line_as_first_visible(first_visible_line, first_visible_line_wrap_ofs);
7310}
7311
7312/* Viewport. */
7313void TextEdit::_update_scrollbars() {
7314 Size2 size = get_size();
7315 Size2 hmin = h_scroll->get_combined_minimum_size();
7316 Size2 vmin = v_scroll->get_combined_minimum_size();
7317
7318 v_scroll->set_begin(Point2(size.width - vmin.width, theme_cache.style_normal->get_margin(SIDE_TOP)));
7319 v_scroll->set_end(Point2(size.width, size.height - theme_cache.style_normal->get_margin(SIDE_TOP) - theme_cache.style_normal->get_margin(SIDE_BOTTOM)));
7320
7321 h_scroll->set_begin(Point2(0, size.height - hmin.height));
7322 h_scroll->set_end(Point2(size.width - vmin.width, size.height));
7323
7324 bool draw_placeholder = text.size() == 1 && text[0].length() == 0;
7325
7326 int visible_rows = get_visible_line_count();
7327 int total_rows = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_total_visible_line_count();
7328 if (scroll_past_end_of_file_enabled) {
7329 total_rows += visible_rows - 1;
7330 }
7331
7332 int visible_width = size.width - theme_cache.style_normal->get_minimum_size().width;
7333 int total_width = (draw_placeholder ? placeholder_max_width : text.get_max_width()) + vmin.x + gutters_width + gutter_padding;
7334
7335 if (draw_minimap) {
7336 total_width += minimap_width;
7337 }
7338
7339 content_height_cache = MAX(total_rows, 1) * get_line_height();
7340 if (fit_content_height) {
7341 update_minimum_size();
7342 }
7343
7344 updating_scrolls = true;
7345
7346 if (total_rows > visible_rows) {
7347 v_scroll->show();
7348 v_scroll->set_max(total_rows + _get_visible_lines_offset());
7349 v_scroll->set_page(visible_rows + _get_visible_lines_offset());
7350 if (smooth_scroll_enabled) {
7351 v_scroll->set_step(0.25);
7352 } else {
7353 v_scroll->set_step(1);
7354 }
7355 set_v_scroll(get_v_scroll());
7356
7357 } else {
7358 first_visible_line = 0;
7359 first_visible_line_wrap_ofs = 0;
7360 v_scroll->set_value(0);
7361 v_scroll->set_max(0);
7362 v_scroll->hide();
7363 }
7364
7365 if (total_width > visible_width) {
7366 h_scroll->show();
7367 h_scroll->set_max(total_width);
7368 h_scroll->set_page(visible_width);
7369 if (first_visible_col > (total_width - visible_width)) {
7370 first_visible_col = (total_width - visible_width);
7371 }
7372 if (fabs(h_scroll->get_value() - (double)first_visible_col) >= 1) {
7373 h_scroll->set_value(first_visible_col);
7374 }
7375
7376 } else {
7377 first_visible_col = 0;
7378 h_scroll->set_value(0);
7379 h_scroll->set_max(0);
7380 h_scroll->hide();
7381 }
7382
7383 updating_scrolls = false;
7384}
7385
7386int TextEdit::_get_control_height() const {
7387 int control_height = get_size().height;
7388 control_height -= theme_cache.style_normal->get_minimum_size().height;
7389 if (h_scroll->is_visible_in_tree()) {
7390 control_height -= h_scroll->get_size().height;
7391 }
7392 return control_height;
7393}
7394
7395void TextEdit::_v_scroll_input() {
7396 scrolling = false;
7397 minimap_clicked = false;
7398}
7399
7400void TextEdit::_scroll_moved(double p_to_val) {
7401 if (updating_scrolls) {
7402 return;
7403 }
7404
7405 if (h_scroll->is_visible_in_tree()) {
7406 first_visible_col = h_scroll->get_value();
7407 }
7408 if (v_scroll->is_visible_in_tree()) {
7409 // Set line ofs and wrap ofs.
7410 bool draw_placeholder = text.size() == 1 && text[0].length() == 0;
7411
7412 int v_scroll_i = floor(get_v_scroll());
7413 int sc = 0;
7414 int n_line;
7415 for (n_line = 0; n_line < text.size(); n_line++) {
7416 if (!_is_line_hidden(n_line)) {
7417 sc++;
7418 sc += draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(n_line);
7419 if (sc > v_scroll_i) {
7420 break;
7421 }
7422 }
7423 }
7424 n_line = MIN(n_line, text.size() - 1);
7425 int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(n_line);
7426 int wi = line_wrap_amount - (sc - v_scroll_i - 1);
7427 wi = CLAMP(wi, 0, line_wrap_amount);
7428
7429 first_visible_line = n_line;
7430 first_visible_line_wrap_ofs = wi;
7431 }
7432 queue_redraw();
7433}
7434
7435double TextEdit::_get_visible_lines_offset() const {
7436 double total = _get_control_height();
7437 total /= (double)get_line_height();
7438 total = total - floor(total);
7439 total = -CLAMP(total, 0.001, 1) + 1;
7440 return total;
7441}
7442
7443double TextEdit::_get_v_scroll_offset() const {
7444 double val = get_v_scroll() - floor(get_v_scroll());
7445 return CLAMP(val, 0, 1);
7446}
7447
7448void TextEdit::_scroll_up(real_t p_delta) {
7449 if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(-p_delta)) {
7450 scrolling = false;
7451 minimap_clicked = false;
7452 }
7453
7454 if (scrolling) {
7455 target_v_scroll = (target_v_scroll - p_delta);
7456 } else {
7457 target_v_scroll = (get_v_scroll() - p_delta);
7458 }
7459
7460 if (smooth_scroll_enabled) {
7461 if (target_v_scroll <= 0) {
7462 target_v_scroll = 0;
7463 }
7464 if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
7465 v_scroll->set_value(target_v_scroll);
7466 } else {
7467 scrolling = true;
7468 set_physics_process_internal(true);
7469 }
7470 } else {
7471 set_v_scroll(target_v_scroll);
7472 }
7473}
7474
7475void TextEdit::_scroll_down(real_t p_delta) {
7476 if (scrolling && smooth_scroll_enabled && SIGN(target_v_scroll - v_scroll->get_value()) != SIGN(p_delta)) {
7477 scrolling = false;
7478 minimap_clicked = false;
7479 }
7480
7481 if (scrolling) {
7482 target_v_scroll = (target_v_scroll + p_delta);
7483 } else {
7484 target_v_scroll = (get_v_scroll() + p_delta);
7485 }
7486
7487 if (smooth_scroll_enabled) {
7488 int max_v_scroll = round(v_scroll->get_max() - v_scroll->get_page());
7489 if (target_v_scroll > max_v_scroll) {
7490 target_v_scroll = max_v_scroll;
7491 }
7492 if (Math::abs(target_v_scroll - v_scroll->get_value()) < 1.0) {
7493 v_scroll->set_value(target_v_scroll);
7494 } else {
7495 scrolling = true;
7496 set_physics_process_internal(true);
7497 }
7498 } else {
7499 set_v_scroll(target_v_scroll);
7500 }
7501}
7502
7503void TextEdit::_scroll_lines_up() {
7504 scrolling = false;
7505 minimap_clicked = false;
7506
7507 // Adjust the vertical scroll.
7508 set_v_scroll(get_v_scroll() - 1);
7509
7510 // Adjust the caret to viewport.
7511 for (int i = 0; i < carets.size(); i++) {
7512 if (has_selection(i)) {
7513 continue;
7514 }
7515
7516 int last_vis_line = get_last_full_visible_line();
7517 int last_vis_wrap = get_last_full_visible_line_wrap_index();
7518 if (get_caret_line(i) > last_vis_line || (get_caret_line(i) == last_vis_line && get_caret_wrap_index(i) > last_vis_wrap)) {
7519 set_caret_line(last_vis_line, false, false, last_vis_wrap, i);
7520 }
7521 }
7522 merge_overlapping_carets();
7523}
7524
7525void TextEdit::_scroll_lines_down() {
7526 scrolling = false;
7527 minimap_clicked = false;
7528
7529 // Adjust the vertical scroll.
7530 set_v_scroll(get_v_scroll() + 1);
7531
7532 // Adjust the caret to viewport.
7533 for (int i = 0; i < carets.size(); i++) {
7534 if (has_selection(i)) {
7535 continue;
7536 }
7537
7538 int first_vis_line = get_first_visible_line();
7539 if (get_caret_line(i) < first_vis_line || (get_caret_line(i) == first_vis_line && get_caret_wrap_index(i) < first_visible_line_wrap_ofs)) {
7540 set_caret_line(first_vis_line, false, false, first_visible_line_wrap_ofs, i);
7541 }
7542 }
7543 merge_overlapping_carets();
7544}
7545
7546// Minimap
7547
7548void TextEdit::_update_minimap_hover() {
7549 const Point2 mp = get_local_mouse_pos();
7550 const int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
7551
7552 const bool hovering_sidebar = mp.x > xmargin_end - minimap_width && mp.x < xmargin_end;
7553 if (!hovering_sidebar) {
7554 if (hovering_minimap) {
7555 // Only redraw if the hovering status changed.
7556 hovering_minimap = false;
7557 queue_redraw();
7558 }
7559
7560 // Return early to avoid running the operations below when not needed.
7561 return;
7562 }
7563
7564 const int row = get_minimap_line_at_pos(mp);
7565
7566 const bool new_hovering_minimap = row >= get_first_visible_line() && row <= get_last_full_visible_line();
7567 if (new_hovering_minimap != hovering_minimap) {
7568 // Only redraw if the hovering status changed.
7569 hovering_minimap = new_hovering_minimap;
7570 queue_redraw();
7571 }
7572}
7573
7574void TextEdit::_update_minimap_click() {
7575 Point2 mp = get_local_mouse_pos();
7576
7577 int xmargin_end = get_size().width - theme_cache.style_normal->get_margin(SIDE_RIGHT);
7578 if (!dragging_minimap && (mp.x < xmargin_end - minimap_width || mp.y > xmargin_end)) {
7579 minimap_clicked = false;
7580 return;
7581 }
7582 minimap_clicked = true;
7583 dragging_minimap = true;
7584
7585 int row = get_minimap_line_at_pos(mp);
7586
7587 if (row >= get_first_visible_line() && (row < get_last_full_visible_line() || row >= (text.size() - 1))) {
7588 minimap_scroll_ratio = v_scroll->get_as_ratio();
7589 minimap_scroll_click_pos = mp.y;
7590 can_drag_minimap = true;
7591 return;
7592 }
7593
7594 Point2i next_line = get_next_visible_line_index_offset_from(row, 0, -get_visible_line_count() / 2);
7595 int first_line = row - next_line.x + 1;
7596 double delta = get_scroll_pos_for_line(first_line, next_line.y) - get_v_scroll();
7597 if (delta < 0) {
7598 _scroll_up(-delta);
7599 } else {
7600 _scroll_down(delta);
7601 }
7602}
7603
7604void TextEdit::_update_minimap_drag() {
7605 if (!can_drag_minimap) {
7606 return;
7607 }
7608
7609 int control_height = _get_control_height();
7610 int scroll_height = v_scroll->get_max() * (minimap_char_size.y + minimap_line_spacing);
7611 if (control_height > scroll_height) {
7612 control_height = scroll_height;
7613 }
7614
7615 Point2 mp = get_local_mouse_pos();
7616
7617 double diff = (mp.y - minimap_scroll_click_pos) / control_height;
7618 v_scroll->set_as_ratio(minimap_scroll_ratio + diff);
7619}
7620
7621/* Gutters. */
7622void TextEdit::_update_gutter_width() {
7623 gutters_width = 0;
7624 for (int i = 0; i < gutters.size(); i++) {
7625 if (gutters[i].draw) {
7626 gutters_width += gutters[i].width;
7627 }
7628 }
7629 if (gutters_width > 0) {
7630 gutter_padding = 2;
7631 }
7632 queue_redraw();
7633}
7634
7635/* Syntax highlighting. */
7636Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) {
7637 return syntax_highlighter.is_null() && !setting_text ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line);
7638}
7639
7640/*** Super internal Core API. Everything builds on it. ***/
7641
7642void TextEdit::_text_changed_emit() {
7643 emit_signal(SNAME("text_changed"));
7644 text_changed_dirty = false;
7645}
7646
7647void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) {
7648 if (!setting_text && idle_detect->is_inside_tree()) {
7649 idle_detect->start();
7650 }
7651
7652 if (undo_enabled) {
7653 _clear_redo();
7654 }
7655
7656 int retline, retchar;
7657 _base_insert_text(p_line, p_char, p_text, retline, retchar);
7658 if (r_end_line) {
7659 *r_end_line = retline;
7660 }
7661 if (r_end_char) {
7662 *r_end_char = retchar;
7663 }
7664
7665 if (!undo_enabled) {
7666 return;
7667 }
7668
7669 /* UNDO!! */
7670 TextOperation op;
7671 op.type = TextOperation::TYPE_INSERT;
7672 op.from_line = p_line;
7673 op.from_column = p_char;
7674 op.to_line = retline;
7675 op.to_column = retchar;
7676 op.text = p_text;
7677 op.version = ++version;
7678 op.chain_forward = false;
7679 op.chain_backward = false;
7680 op.start_carets = carets;
7681 op.end_carets = carets;
7682
7683 // See if it should just be set as current op.
7684 if (current_op.type != op.type) {
7685 op.prev_version = get_version();
7686 _push_current_op();
7687 current_op = op;
7688
7689 return; // Set as current op, return.
7690 }
7691 // See if it can be merged.
7692 if (current_op.to_line != p_line || current_op.to_column != p_char) {
7693 op.prev_version = get_version();
7694 _push_current_op();
7695 current_op = op;
7696 return; // Set as current op, return.
7697 }
7698 // Merge current op.
7699
7700 current_op.text += p_text;
7701 current_op.to_column = retchar;
7702 current_op.to_line = retline;
7703 current_op.version = op.version;
7704 current_op.end_carets = carets;
7705}
7706
7707void TextEdit::_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
7708 if (!setting_text && idle_detect->is_inside_tree()) {
7709 idle_detect->start();
7710 }
7711
7712 String txt;
7713 if (undo_enabled) {
7714 _clear_redo();
7715 txt = _base_get_text(p_from_line, p_from_column, p_to_line, p_to_column);
7716 }
7717
7718 _base_remove_text(p_from_line, p_from_column, p_to_line, p_to_column);
7719
7720 if (!undo_enabled) {
7721 return;
7722 }
7723
7724 /* UNDO! */
7725 TextOperation op;
7726 op.type = TextOperation::TYPE_REMOVE;
7727 op.from_line = p_from_line;
7728 op.from_column = p_from_column;
7729 op.to_line = p_to_line;
7730 op.to_column = p_to_column;
7731 op.text = txt;
7732 op.version = ++version;
7733 op.chain_forward = false;
7734 op.chain_backward = false;
7735 op.start_carets = carets;
7736 op.end_carets = carets;
7737
7738 // See if it should just be set as current op.
7739 if (current_op.type != op.type) {
7740 op.prev_version = get_version();
7741 _push_current_op();
7742 current_op = op;
7743 return; // Set as current op, return.
7744 }
7745 // See if it can be merged.
7746 if (current_op.from_line == p_to_line && current_op.from_column == p_to_column) {
7747 // Backspace or similar.
7748 current_op.text = txt + current_op.text;
7749 current_op.from_line = p_from_line;
7750 current_op.from_column = p_from_column;
7751 current_op.end_carets = carets;
7752 return; // Update current op.
7753 }
7754
7755 op.prev_version = get_version();
7756 _push_current_op();
7757 current_op = op;
7758}
7759
7760void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column) {
7761 // Save for undo.
7762 ERR_FAIL_INDEX(p_line, text.size());
7763 ERR_FAIL_COND(p_char < 0);
7764
7765 /* STEP 1: Remove \r from source text and separate in substrings. */
7766 const String text_to_insert = p_text.replace("\r", "");
7767 Vector<String> substrings = text_to_insert.split("\n");
7768
7769 // Is this just a new empty line?
7770 bool shift_first_line = p_char == 0 && substrings.size() == 2 && text_to_insert == "\n";
7771
7772 /* STEP 2: Add spaces if the char is greater than the end of the line. */
7773 while (p_char > text[p_line].length()) {
7774 text.set(p_line, text[p_line] + String::chr(' '), structured_text_parser(st_parser, st_args, text[p_line] + String::chr(' ')));
7775 }
7776
7777 /* STEP 3: Separate dest string in pre and post text. */
7778 String postinsert_text = text[p_line].substr(p_char, text[p_line].size());
7779
7780 substrings.write[0] = text[p_line].substr(0, p_char) + substrings[0];
7781 substrings.write[substrings.size() - 1] += postinsert_text;
7782
7783 Vector<Array> bidi_override;
7784 bidi_override.resize(substrings.size());
7785 for (int i = 0; i < substrings.size(); i++) {
7786 bidi_override.write[i] = structured_text_parser(st_parser, st_args, substrings[i]);
7787 }
7788
7789 text.insert(p_line, substrings, bidi_override);
7790
7791 if (shift_first_line) {
7792 text.move_gutters(p_line, p_line + 1);
7793 text.set_hidden(p_line + 1, text.is_hidden(p_line));
7794
7795 text.set_hidden(p_line, false);
7796 }
7797
7798 r_end_line = p_line + substrings.size() - 1;
7799 r_end_column = text[r_end_line].length() - postinsert_text.length();
7800
7801 TextServer::Direction dir = TS->shaped_text_get_dominant_direction_in_range(text.get_line_data(r_end_line)->get_rid(), (r_end_line == p_line) ? carets[0].column : 0, r_end_column);
7802 if (dir != TextServer::DIRECTION_AUTO) {
7803 input_direction = (TextDirection)dir;
7804 }
7805
7806 if (!text_changed_dirty && !setting_text) {
7807 if (is_inside_tree()) {
7808 MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
7809 }
7810 text_changed_dirty = true;
7811 }
7812 emit_signal(SNAME("lines_edited_from"), p_line, r_end_line);
7813}
7814
7815String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const {
7816 ERR_FAIL_INDEX_V(p_from_line, text.size(), String());
7817 ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, String());
7818 ERR_FAIL_INDEX_V(p_to_line, text.size(), String());
7819 ERR_FAIL_INDEX_V(p_to_column, text[p_to_line].length() + 1, String());
7820 ERR_FAIL_COND_V(p_to_line < p_from_line, String()); // 'from > to'.
7821 ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column < p_from_column, String()); // 'from > to'.
7822
7823 StringBuilder ret;
7824
7825 for (int i = p_from_line; i <= p_to_line; i++) {
7826 int begin = (i == p_from_line) ? p_from_column : 0;
7827 int end = (i == p_to_line) ? p_to_column : text[i].length();
7828
7829 if (i > p_from_line) {
7830 ret += "\n";
7831 }
7832 ret += text[i].substr(begin, end - begin);
7833 }
7834
7835 return ret.as_string();
7836}
7837
7838void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) {
7839 ERR_FAIL_INDEX(p_from_line, text.size());
7840 ERR_FAIL_INDEX(p_from_column, text[p_from_line].length() + 1);
7841 ERR_FAIL_INDEX(p_to_line, text.size());
7842 ERR_FAIL_INDEX(p_to_column, text[p_to_line].length() + 1);
7843 ERR_FAIL_COND(p_to_line < p_from_line); // 'from > to'.
7844 ERR_FAIL_COND(p_to_line == p_from_line && p_to_column < p_from_column); // 'from > to'.
7845
7846 String pre_text = text[p_from_line].substr(0, p_from_column);
7847 String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length());
7848
7849 text.remove_range(p_from_line, p_to_line);
7850 text.set(p_from_line, pre_text + post_text, structured_text_parser(st_parser, st_args, pre_text + post_text));
7851
7852 if (!text_changed_dirty && !setting_text) {
7853 if (is_inside_tree()) {
7854 MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");
7855 }
7856 text_changed_dirty = true;
7857 }
7858 emit_signal(SNAME("lines_edited_from"), p_to_line, p_from_line);
7859}
7860
7861TextEdit::TextEdit(const String &p_placeholder) {
7862 placeholder_data_buf.instantiate();
7863 carets.push_back(Caret());
7864
7865 clear();
7866 set_focus_mode(FOCUS_ALL);
7867 set_default_cursor_shape(CURSOR_IBEAM);
7868 set_process_unhandled_key_input(true);
7869
7870 text.set_tab_size(text.get_tab_size());
7871
7872 h_scroll = memnew(HScrollBar);
7873 v_scroll = memnew(VScrollBar);
7874
7875 add_child(h_scroll, false, INTERNAL_MODE_FRONT);
7876 add_child(v_scroll, false, INTERNAL_MODE_FRONT);
7877
7878 h_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
7879 v_scroll->connect("value_changed", callable_mp(this, &TextEdit::_scroll_moved));
7880
7881 v_scroll->connect("scrolling", callable_mp(this, &TextEdit::_v_scroll_input));
7882
7883 /* Caret. */
7884 caret_blink_timer = memnew(Timer);
7885 add_child(caret_blink_timer, false, INTERNAL_MODE_FRONT);
7886 caret_blink_timer->set_wait_time(0.65);
7887 caret_blink_timer->connect("timeout", callable_mp(this, &TextEdit::_toggle_draw_caret));
7888 set_caret_blink_enabled(false);
7889
7890 /* Selection. */
7891 click_select_held = memnew(Timer);
7892 add_child(click_select_held, false, INTERNAL_MODE_FRONT);
7893 click_select_held->set_wait_time(0.05);
7894 click_select_held->connect("timeout", callable_mp(this, &TextEdit::_click_selection_held));
7895
7896 idle_detect = memnew(Timer);
7897 add_child(idle_detect, false, INTERNAL_MODE_FRONT);
7898 idle_detect->set_one_shot(true);
7899 idle_detect->set_wait_time(GLOBAL_GET("gui/timers/text_edit_idle_detect_sec"));
7900 idle_detect->connect("timeout", callable_mp(this, &TextEdit::_push_current_op));
7901
7902 undo_stack_max_size = GLOBAL_GET("gui/common/text_edit_undo_stack_max_size");
7903
7904 set_placeholder(p_placeholder);
7905
7906 set_editable(true);
7907}
7908