1/**************************************************************************/
2/* code_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 "code_edit.h"
32#include "code_edit.compat.inc"
33
34#include "core/os/keyboard.h"
35#include "core/string/string_builder.h"
36#include "core/string/ustring.h"
37#include "scene/theme/theme_db.h"
38
39void CodeEdit::_notification(int p_what) {
40 switch (p_what) {
41 case NOTIFICATION_THEME_CHANGED: {
42 set_gutter_width(main_gutter, get_line_height());
43 set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
44 set_gutter_width(fold_gutter, get_line_height() / 1.2);
45 } break;
46
47 case NOTIFICATION_DRAW: {
48 RID ci = get_canvas_item();
49 const Size2 size = get_size();
50 const bool caret_visible = is_caret_visible();
51 const bool rtl = is_layout_rtl();
52 const int row_height = get_line_height();
53
54 if (line_length_guideline_columns.size() > 0) {
55 const int xmargin_beg = theme_cache.style_normal->get_margin(SIDE_LEFT) + get_total_gutter_width();
56 const int xmargin_end = size.width - theme_cache.style_normal->get_margin(SIDE_RIGHT) - (is_drawing_minimap() ? get_minimap_width() : 0);
57 const float char_size = theme_cache.font->get_char_size('0', theme_cache.font_size).width;
58
59 for (int i = 0; i < line_length_guideline_columns.size(); i++) {
60 const int xoffset = xmargin_beg + char_size * (int)line_length_guideline_columns[i] - get_h_scroll();
61 if (xoffset > xmargin_beg && xoffset < xmargin_end) {
62 Color guideline_color = (i == 0) ? theme_cache.line_length_guideline_color : theme_cache.line_length_guideline_color * Color(1, 1, 1, 0.5);
63 if (rtl) {
64 RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(size.width - xoffset, 0), Point2(size.width - xoffset, size.height), guideline_color);
65 continue;
66 }
67 RenderingServer::get_singleton()->canvas_item_add_line(ci, Point2(xoffset, 0), Point2(xoffset, size.height), guideline_color);
68 }
69 }
70 }
71
72 bool code_completion_below = false;
73 if (caret_visible && code_completion_active && code_completion_options.size() > 0) {
74 const int code_completion_options_count = code_completion_options.size();
75 const int lines = MIN(code_completion_options_count, theme_cache.code_completion_max_lines);
76 const Size2 icon_area_size(row_height, row_height);
77
78 code_completion_rect.size.width = code_completion_longest_line + theme_cache.code_completion_icon_separation + icon_area_size.width + 2;
79 code_completion_rect.size.height = lines * row_height;
80
81 const Point2 caret_pos = get_caret_draw_pos();
82 const int total_height = theme_cache.code_completion_style->get_minimum_size().y + code_completion_rect.size.height;
83 const bool can_fit_completion_above = (caret_pos.y - row_height > total_height);
84 const bool can_fit_completion_below = (caret_pos.y + row_height + total_height <= get_size().height);
85 if (!can_fit_completion_below && can_fit_completion_above) {
86 code_completion_rect.position.y = (caret_pos.y - total_height - row_height) + theme_cache.line_spacing;
87 } else {
88 code_completion_rect.position.y = caret_pos.y + (theme_cache.line_spacing / 2.0f);
89 code_completion_below = true;
90 }
91
92 const int scroll_width = code_completion_options_count > theme_cache.code_completion_max_lines ? theme_cache.code_completion_scroll_width : 0;
93 const int code_completion_base_width = theme_cache.font->get_string_size(code_completion_base, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
94 if (caret_pos.x - code_completion_base_width + code_completion_rect.size.width + scroll_width > get_size().width) {
95 code_completion_rect.position.x = get_size().width - code_completion_rect.size.width - scroll_width;
96 } else {
97 code_completion_rect.position.x = caret_pos.x - code_completion_base_width;
98 }
99
100 draw_style_box(theme_cache.code_completion_style, Rect2(code_completion_rect.position - theme_cache.code_completion_style->get_offset(), code_completion_rect.size + theme_cache.code_completion_style->get_minimum_size() + Size2(scroll_width, 0)));
101 if (theme_cache.code_completion_background_color.a > 0.01) {
102 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(code_completion_rect.position, code_completion_rect.size + Size2(scroll_width, 0)), theme_cache.code_completion_background_color);
103 }
104
105 code_completion_scroll_rect.position = code_completion_rect.position + Vector2(code_completion_rect.size.width, 0);
106 code_completion_scroll_rect.size = Vector2(scroll_width, code_completion_rect.size.height);
107
108 code_completion_line_ofs = CLAMP((code_completion_force_item_center < 0 ? code_completion_current_selected : code_completion_force_item_center) - lines / 2, 0, code_completion_options_count - lines);
109 RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(code_completion_rect.position.x, code_completion_rect.position.y + (code_completion_current_selected - code_completion_line_ofs) * row_height), Size2(code_completion_rect.size.width, row_height)), theme_cache.code_completion_selected_color);
110
111 for (int i = 0; i < lines; i++) {
112 int l = code_completion_line_ofs + i;
113 ERR_CONTINUE(l < 0 || l >= code_completion_options_count);
114
115 Ref<TextLine> tl;
116 tl.instantiate();
117 tl->add_string(code_completion_options[l].display, theme_cache.font, theme_cache.font_size);
118
119 int yofs = (row_height - tl->get_size().y) / 2;
120 Point2 title_pos(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height + yofs);
121
122 /* Draw completion icon if it is valid. */
123 const Ref<Texture2D> &icon = code_completion_options[l].icon;
124 Rect2 icon_area(code_completion_rect.position.x, code_completion_rect.position.y + i * row_height, icon_area_size.width, icon_area_size.height);
125 if (icon.is_valid()) {
126 Size2 icon_size = icon_area.size * 0.7;
127 icon->draw_rect(ci, Rect2(icon_area.position + (icon_area.size - icon_size) / 2, icon_size));
128 }
129 title_pos.x = icon_area.position.x + icon_area.size.width + theme_cache.code_completion_icon_separation;
130
131 tl->set_width(code_completion_rect.size.width - (icon_area_size.x + theme_cache.code_completion_icon_separation));
132 if (rtl) {
133 if (code_completion_options[l].default_value.get_type() == Variant::COLOR) {
134 draw_rect(Rect2(Point2(code_completion_rect.position.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value);
135 }
136 tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
137 } else {
138 if (code_completion_options[l].default_value.get_type() == Variant::COLOR) {
139 draw_rect(Rect2(Point2(code_completion_rect.position.x + code_completion_rect.size.width - icon_area_size.x, icon_area.position.y), icon_area_size), (Color)code_completion_options[l].default_value);
140 }
141 tl->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
142 }
143
144 Point2 match_pos = Point2(code_completion_rect.position.x + icon_area_size.x + theme_cache.code_completion_icon_separation, code_completion_rect.position.y + i * row_height);
145
146 for (int j = 0; j < code_completion_options[l].matches.size(); j++) {
147 Pair<int, int> match_segment = code_completion_options[l].matches[j];
148 int match_offset = theme_cache.font->get_string_size(code_completion_options[l].display.substr(0, match_segment.first), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
149 int match_len = theme_cache.font->get_string_size(code_completion_options[l].display.substr(match_segment.first, match_segment.second), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width;
150
151 draw_rect(Rect2(match_pos + Point2(match_offset, 0), Size2(match_len, row_height)), theme_cache.code_completion_existing_color);
152 }
153 tl->draw(ci, title_pos, code_completion_options[l].font_color);
154 }
155
156 /* Draw a small scroll rectangle to show a position in the options. */
157 if (scroll_width) {
158 Color scroll_color = is_code_completion_scroll_hovered || is_code_completion_scroll_pressed ? theme_cache.code_completion_scroll_hovered_color : theme_cache.code_completion_scroll_color;
159
160 float r = (float)theme_cache.code_completion_max_lines / code_completion_options_count;
161 float o = (float)code_completion_line_ofs / code_completion_options_count;
162 draw_rect(Rect2(code_completion_rect.position.x + code_completion_rect.size.width, code_completion_rect.position.y + o * code_completion_rect.size.y, scroll_width, code_completion_rect.size.y * r), scroll_color);
163 }
164 }
165
166 /* Code hint */
167 if (caret_visible && !code_hint.is_empty() && (!code_completion_active || (code_completion_below != code_hint_draw_below))) {
168 const int font_height = theme_cache.font->get_height(theme_cache.font_size);
169
170 Vector<String> code_hint_lines = code_hint.split("\n");
171 int line_count = code_hint_lines.size();
172
173 int max_width = 0;
174 for (int i = 0; i < line_count; i++) {
175 max_width = MAX(max_width, theme_cache.font->get_string_size(code_hint_lines[i], HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x);
176 }
177 Size2 minsize = theme_cache.code_hint_style->get_minimum_size() + Size2(max_width, line_count * font_height + (theme_cache.line_spacing * line_count - 1));
178
179 int offset = theme_cache.font->get_string_size(code_hint_lines[0].substr(0, code_hint_lines[0].find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
180 if (code_hint_xpos == -0xFFFF) {
181 code_hint_xpos = get_caret_draw_pos().x - offset;
182 }
183 Point2 hint_ofs = Vector2(code_hint_xpos, get_caret_draw_pos().y);
184 if (code_hint_draw_below) {
185 hint_ofs.y += theme_cache.line_spacing / 2.0f;
186 } else {
187 hint_ofs.y -= (minsize.y + row_height) - theme_cache.line_spacing;
188 }
189
190 draw_style_box(theme_cache.code_hint_style, Rect2(hint_ofs, minsize));
191
192 int yofs = 0;
193 for (int i = 0; i < line_count; i++) {
194 const String &line = code_hint_lines[i];
195
196 int begin = 0;
197 int end = 0;
198 if (line.contains(String::chr(0xFFFF))) {
199 begin = theme_cache.font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
200 end = theme_cache.font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).x;
201 }
202
203 Point2 round_ofs = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(0, theme_cache.font->get_ascent(theme_cache.font_size) + font_height * i + yofs);
204 round_ofs = round_ofs.round();
205 draw_string(theme_cache.font, round_ofs, line.replace(String::chr(0xFFFF), ""), HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size, theme_cache.code_hint_color);
206 if (end > 0) {
207 // Draw an underline for the currently edited function parameter.
208 const Vector2 b = hint_ofs + theme_cache.code_hint_style->get_offset() + Vector2(begin, font_height + font_height * i + yofs);
209 draw_line(b, b + Vector2(end - begin, 0), theme_cache.code_hint_color, 2);
210
211 // Draw a translucent text highlight as well.
212 const Rect2 highlight_rect = Rect2(
213 b - Vector2(0, font_height),
214 Vector2(end - begin, font_height));
215 draw_rect(highlight_rect, theme_cache.code_hint_color * Color(1, 1, 1, 0.2));
216 }
217 yofs += theme_cache.line_spacing;
218 }
219 }
220 } break;
221
222 case NOTIFICATION_DRAG_BEGIN: {
223 cancel_code_completion();
224 } break;
225
226 case NOTIFICATION_MOUSE_EXIT: {
227 queue_redraw();
228 } break;
229 }
230}
231
232void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
233 Ref<InputEventMouseButton> mb = p_gui_input;
234 if (mb.is_valid()) {
235 /* Ignore mouse clicks in IME input mode. */
236 if (has_ime_text()) {
237 return;
238 }
239
240 if (is_code_completion_scroll_pressed && mb->get_button_index() == MouseButton::LEFT) {
241 is_code_completion_scroll_pressed = false;
242 accept_event();
243 queue_redraw();
244 return;
245 }
246
247 if (is_code_completion_drag_started && !mb->is_pressed()) {
248 is_code_completion_drag_started = false;
249 accept_event();
250 queue_redraw();
251 return;
252 }
253
254 if (code_completion_active && code_completion_rect.has_point(mb->get_position())) {
255 if (!mb->is_pressed()) {
256 accept_event();
257 return;
258 }
259 is_code_completion_drag_started = true;
260
261 switch (mb->get_button_index()) {
262 case MouseButton::WHEEL_UP: {
263 if (code_completion_current_selected > 0) {
264 code_completion_current_selected--;
265 code_completion_force_item_center = -1;
266 queue_redraw();
267 }
268 } break;
269 case MouseButton::WHEEL_DOWN: {
270 if (code_completion_current_selected < code_completion_options.size() - 1) {
271 code_completion_current_selected++;
272 code_completion_force_item_center = -1;
273 queue_redraw();
274 }
275 } break;
276 case MouseButton::LEFT: {
277 if (code_completion_force_item_center == -1) {
278 code_completion_force_item_center = code_completion_current_selected;
279 }
280
281 code_completion_current_selected = CLAMP(code_completion_line_ofs + (mb->get_position().y - code_completion_rect.position.y) / get_line_height(), 0, code_completion_options.size() - 1);
282 if (mb->is_double_click()) {
283 confirm_code_completion();
284 }
285 queue_redraw();
286 } break;
287 default:
288 break;
289 }
290
291 accept_event();
292 return;
293 } else if (code_completion_active && code_completion_scroll_rect.has_point(mb->get_position())) {
294 if (mb->get_button_index() != MouseButton::LEFT) {
295 accept_event();
296 return;
297 }
298
299 if (mb->is_pressed()) {
300 is_code_completion_drag_started = true;
301 is_code_completion_scroll_pressed = true;
302
303 _update_scroll_selected_line(mb->get_position().y);
304 queue_redraw();
305 }
306
307 accept_event();
308 return;
309 }
310
311 cancel_code_completion();
312 set_code_hint("");
313
314 if (mb->is_pressed()) {
315 Vector2i mpos = mb->get_position();
316 if (is_layout_rtl()) {
317 mpos.x = get_size().x - mpos.x;
318 }
319
320 Point2i pos = get_line_column_at_pos(mpos, false);
321 int line = pos.y;
322 int col = pos.x;
323
324 if (line != -1 && mb->get_button_index() == MouseButton::LEFT) {
325 if (is_line_folded(line)) {
326 int wrap_index = get_line_wrap_index_at_column(line, col);
327 if (wrap_index == get_line_wrap_count(line)) {
328 int eol_icon_width = theme_cache.folded_eol_icon->get_width();
329 int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
330 if (mpos.x > left_margin && mpos.x <= left_margin + eol_icon_width + 3) {
331 unfold_line(line);
332 return;
333 }
334 }
335 }
336 }
337 } else {
338 if (mb->get_button_index() == MouseButton::LEFT) {
339 if (mb->is_command_or_control_pressed() && !symbol_lookup_word.is_empty()) {
340 Vector2i mpos = mb->get_position();
341 if (is_layout_rtl()) {
342 mpos.x = get_size().x - mpos.x;
343 }
344
345 Point2i pos = get_line_column_at_pos(mpos, false);
346 int line = pos.y;
347 int col = pos.x;
348
349 if (line != -1) {
350 emit_signal(SNAME("symbol_lookup"), symbol_lookup_word, line, col);
351 }
352 return;
353 }
354 }
355 }
356 }
357
358 Ref<InputEventMouseMotion> mm = p_gui_input;
359 if (mm.is_valid()) {
360 Vector2i mpos = mm->get_position();
361 if (is_layout_rtl()) {
362 mpos.x = get_size().x - mpos.x;
363 }
364
365 if (symbol_lookup_on_click_enabled) {
366 if (mm->is_command_or_control_pressed() && mm->get_button_mask().is_empty()) {
367 symbol_lookup_pos = get_line_column_at_pos(mpos);
368 symbol_lookup_new_word = get_word_at_pos(mpos);
369 if (symbol_lookup_new_word != symbol_lookup_word) {
370 emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word);
371 }
372 } else if (!mm->is_command_or_control_pressed() || (!mm->get_button_mask().is_empty() && symbol_lookup_pos != get_line_column_at_pos(mpos))) {
373 set_symbol_lookup_word_as_valid(false);
374 }
375 }
376
377 bool scroll_hovered = code_completion_scroll_rect.has_point(mpos);
378 if (is_code_completion_scroll_hovered != scroll_hovered) {
379 is_code_completion_scroll_hovered = scroll_hovered;
380 accept_event();
381 queue_redraw();
382 }
383
384 if (is_code_completion_scroll_pressed) {
385 _update_scroll_selected_line(mpos.y);
386 accept_event();
387 queue_redraw();
388 return;
389 }
390
391 if (code_completion_active && code_completion_rect.has_point(mm->get_position())) {
392 accept_event();
393 return;
394 }
395 }
396
397 Ref<InputEventKey> k = p_gui_input;
398 if (TextEdit::alt_input(p_gui_input)) {
399 accept_event();
400 return;
401 }
402
403 bool update_code_completion = false;
404 if (!k.is_valid()) {
405 // MouseMotion events should not be handled by TextEdit logic if we're
406 // currently clicking and dragging from the code completion panel.
407 if (!mm.is_valid() || !is_code_completion_drag_started) {
408 TextEdit::gui_input(p_gui_input);
409 }
410 return;
411 }
412
413 /* Ctrl + Hover symbols */
414 bool mac_keys = OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios");
415 if ((mac_keys && k->get_keycode() == Key::META) || (!mac_keys && k->get_keycode() == Key::CTRL)) {
416 if (symbol_lookup_on_click_enabled) {
417 if (k->is_pressed() && !is_dragging_cursor()) {
418 symbol_lookup_new_word = get_word_at_pos(get_local_mouse_pos());
419 if (symbol_lookup_new_word != symbol_lookup_word) {
420 emit_signal(SNAME("symbol_validate"), symbol_lookup_new_word);
421 }
422 } else {
423 set_symbol_lookup_word_as_valid(false);
424 }
425 }
426 return;
427 }
428
429 /* If a modifier has been pressed, and nothing else, return. */
430 if (!k->is_pressed() || 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) {
431 return;
432 }
433
434 // Allow unicode handling if:
435 // No modifiers are pressed (except Shift and CapsLock)
436 bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
437
438 /* AUTO-COMPLETE */
439 if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) {
440 request_code_completion(true);
441 accept_event();
442 return;
443 }
444
445 if (code_completion_active) {
446 if (k->is_action("ui_up", true)) {
447 if (code_completion_current_selected > 0) {
448 code_completion_current_selected--;
449 } else {
450 code_completion_current_selected = code_completion_options.size() - 1;
451 }
452 code_completion_force_item_center = -1;
453 queue_redraw();
454 accept_event();
455 return;
456 }
457 if (k->is_action("ui_down", true)) {
458 if (code_completion_current_selected < code_completion_options.size() - 1) {
459 code_completion_current_selected++;
460 } else {
461 code_completion_current_selected = 0;
462 }
463 code_completion_force_item_center = -1;
464 queue_redraw();
465 accept_event();
466 return;
467 }
468 if (k->is_action("ui_page_up", true)) {
469 code_completion_current_selected = MAX(0, code_completion_current_selected - theme_cache.code_completion_max_lines);
470 code_completion_force_item_center = -1;
471 queue_redraw();
472 accept_event();
473 return;
474 }
475 if (k->is_action("ui_page_down", true)) {
476 code_completion_current_selected = MIN(code_completion_options.size() - 1, code_completion_current_selected + theme_cache.code_completion_max_lines);
477 code_completion_force_item_center = -1;
478 queue_redraw();
479 accept_event();
480 return;
481 }
482 if (k->is_action("ui_home", true)) {
483 code_completion_current_selected = 0;
484 code_completion_force_item_center = -1;
485 queue_redraw();
486 accept_event();
487 return;
488 }
489 if (k->is_action("ui_end", true)) {
490 code_completion_current_selected = code_completion_options.size() - 1;
491 code_completion_force_item_center = -1;
492 queue_redraw();
493 accept_event();
494 return;
495 }
496 if (k->is_action("ui_text_completion_replace", true) || k->is_action("ui_text_completion_accept", true)) {
497 confirm_code_completion(k->is_action("ui_text_completion_replace", true));
498 accept_event();
499 return;
500 }
501 if (k->is_action("ui_cancel", true)) {
502 cancel_code_completion();
503 accept_event();
504 return;
505 }
506 if (k->is_action("ui_text_backspace", true)) {
507 backspace();
508 _filter_code_completion_candidates_impl();
509 accept_event();
510 return;
511 }
512
513 if (k->is_action("ui_left", true) || k->is_action("ui_right", true)) {
514 update_code_completion = true;
515 } else {
516 update_code_completion = (allow_unicode_handling && k->get_unicode() >= 32);
517 }
518
519 if (!update_code_completion) {
520 cancel_code_completion();
521 }
522 }
523
524 /* MISC */
525 if (!code_hint.is_empty() && k->is_action("ui_cancel", true)) {
526 set_code_hint("");
527 accept_event();
528 return;
529 }
530 if (allow_unicode_handling && k->get_unicode() == ')') {
531 set_code_hint("");
532 }
533
534 /* Indentation */
535 if (k->is_action("ui_text_indent", true)) {
536 do_indent();
537 accept_event();
538 return;
539 }
540
541 if (k->is_action("ui_text_dedent", true)) {
542 unindent_lines();
543 accept_event();
544 return;
545 }
546
547 // Override new line actions, for auto indent.
548 if (k->is_action("ui_text_newline_above", true)) {
549 _new_line(false, true);
550 accept_event();
551 return;
552 }
553 if (k->is_action("ui_text_newline_blank", true)) {
554 _new_line(false);
555 accept_event();
556 return;
557 }
558 if (k->is_action("ui_text_newline", true)) {
559 _new_line();
560 accept_event();
561 return;
562 }
563
564 // Remove shift, otherwise actions will not match.
565 k = k->duplicate();
566 k->set_shift_pressed(false);
567
568 if (k->is_action("ui_text_caret_up", true) ||
569 k->is_action("ui_text_caret_down", true) ||
570 k->is_action("ui_text_caret_line_start", true) ||
571 k->is_action("ui_text_caret_line_end", true) ||
572 k->is_action("ui_text_caret_page_up", true) ||
573 k->is_action("ui_text_caret_page_down", true)) {
574 set_code_hint("");
575 }
576
577 TextEdit::gui_input(p_gui_input);
578
579 if (update_code_completion) {
580 _filter_code_completion_candidates_impl();
581 }
582}
583
584/* General overrides */
585Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
586 if (!symbol_lookup_word.is_empty()) {
587 return CURSOR_POINTING_HAND;
588 }
589
590 if ((code_completion_active && code_completion_rect.has_point(p_pos)) || (!is_editable() && (!is_selecting_enabled() || get_line_count() == 0))) {
591 return CURSOR_ARROW;
592 }
593
594 if (code_completion_active && code_completion_scroll_rect.has_point(p_pos)) {
595 return CURSOR_ARROW;
596 }
597
598 Point2i pos = get_line_column_at_pos(p_pos, false);
599 int line = pos.y;
600 int col = pos.x;
601
602 if (line != -1 && is_line_folded(line)) {
603 int wrap_index = get_line_wrap_index_at_column(line, col);
604 if (wrap_index == get_line_wrap_count(line)) {
605 int eol_icon_width = theme_cache.folded_eol_icon->get_width();
606 int left_margin = get_total_gutter_width() + eol_icon_width + get_line_width(line, wrap_index) - get_h_scroll();
607 if (p_pos.x > left_margin && p_pos.x <= left_margin + eol_icon_width + 3) {
608 return CURSOR_POINTING_HAND;
609 }
610 }
611 }
612
613 return TextEdit::get_cursor_shape(p_pos);
614}
615
616/* Text manipulation */
617
618// Overridable actions
619void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) {
620 start_action(EditAction::ACTION_TYPING);
621 Vector<int> caret_edit_order = get_caret_index_edit_order();
622 for (const int &i : caret_edit_order) {
623 if (p_caret != -1 && p_caret != i) {
624 continue;
625 }
626
627 bool had_selection = has_selection(i);
628 String selection_text = (had_selection ? get_selected_text(i) : "");
629
630 if (had_selection) {
631 delete_selection(i);
632 }
633
634 // Remove the old character if in overtype mode and no selection.
635 if (is_overtype_mode_enabled() && !had_selection) {
636 // Make sure we don't try and remove empty space.
637 if (get_caret_column(i) < get_line(get_caret_line(i)).length()) {
638 remove_text(get_caret_line(i), get_caret_column(i), get_caret_line(i), get_caret_column(i) + 1);
639 }
640 }
641
642 const char32_t chr[2] = { (char32_t)p_unicode, 0 };
643
644 if (auto_brace_completion_enabled) {
645 int cl = get_caret_line(i);
646 int cc = get_caret_column(i);
647
648 if (had_selection) {
649 insert_text_at_caret(chr, i);
650
651 String close_key = get_auto_brace_completion_close_key(chr);
652 if (!close_key.is_empty()) {
653 insert_text_at_caret(selection_text + close_key, i);
654 set_caret_column(get_caret_column(i) - 1, i == 0, i);
655 }
656 } else {
657 int caret_move_offset = 1;
658
659 int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
660
661 if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
662 insert_text_at_caret(chr, i);
663 } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
664 insert_text_at_caret(chr, i);
665 } else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
666 caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
667 } else if (is_in_comment(cl, cc) != -1 || (is_in_string(cl, cc) != -1 && has_string_delimiter(chr))) {
668 insert_text_at_caret(chr, i);
669 } else {
670 insert_text_at_caret(chr, i);
671
672 int pre_brace_pair = _get_auto_brace_pair_open_at_pos(cl, cc + 1);
673 if (pre_brace_pair != -1) {
674 insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
675 }
676 }
677 set_caret_column(cc + caret_move_offset, i == 0, i);
678 }
679 } else {
680 insert_text_at_caret(chr, i);
681 }
682 }
683 end_action();
684}
685
686void CodeEdit::_backspace_internal(int p_caret) {
687 if (!is_editable()) {
688 return;
689 }
690
691 if (has_selection(p_caret)) {
692 delete_selection(p_caret);
693 return;
694 }
695
696 begin_complex_operation();
697 Vector<int> caret_edit_order = get_caret_index_edit_order();
698 for (const int &i : caret_edit_order) {
699 if (p_caret != -1 && p_caret != i) {
700 continue;
701 }
702
703 int cc = get_caret_column(i);
704 int cl = get_caret_line(i);
705
706 if (cc == 0 && cl == 0) {
707 continue;
708 }
709
710 if (cl > 0 && _is_line_hidden(cl - 1)) {
711 unfold_line(get_caret_line(i) - 1);
712 }
713
714 int prev_line = cc ? cl : cl - 1;
715 int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
716
717 merge_gutters(prev_line, cl);
718
719 if (auto_brace_completion_enabled && cc > 0) {
720 int idx = _get_auto_brace_pair_open_at_pos(cl, cc);
721 if (idx != -1) {
722 prev_column = cc - auto_brace_completion_pairs[idx].open_key.length();
723
724 if (_get_auto_brace_pair_close_at_pos(cl, cc) == idx) {
725 remove_text(prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
726 } else {
727 remove_text(prev_line, prev_column, cl, cc);
728 }
729 set_caret_line(prev_line, false, true, 0, i);
730 set_caret_column(prev_column, i == 0, i);
731
732 adjust_carets_after_edit(i, prev_line, prev_column, cl, cc + auto_brace_completion_pairs[idx].close_key.length());
733 continue;
734 }
735 }
736
737 // For space indentation we need to do a basic unindent if there are no chars to the left, acting the same way as tabs.
738 if (indent_using_spaces && cc != 0) {
739 if (get_first_non_whitespace_column(cl) >= cc) {
740 prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
741 prev_line = cl;
742 }
743 }
744
745 remove_text(prev_line, prev_column, cl, cc);
746
747 set_caret_line(prev_line, false, true, 0, i);
748 set_caret_column(prev_column, i == 0, i);
749
750 adjust_carets_after_edit(i, prev_line, prev_column, cl, cc);
751 }
752 merge_overlapping_carets();
753 end_complex_operation();
754}
755
756/* Indent management */
757void CodeEdit::set_indent_size(const int p_size) {
758 ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
759 if (indent_size == p_size) {
760 return;
761 }
762
763 indent_size = p_size;
764 if (indent_using_spaces) {
765 indent_text = String(" ").repeat(p_size);
766 } else {
767 indent_text = "\t";
768 }
769 set_tab_size(p_size);
770}
771
772int CodeEdit::get_indent_size() const {
773 return indent_size;
774}
775
776void CodeEdit::set_indent_using_spaces(const bool p_use_spaces) {
777 indent_using_spaces = p_use_spaces;
778 if (indent_using_spaces) {
779 indent_text = String(" ").repeat(indent_size);
780 } else {
781 indent_text = "\t";
782 }
783}
784
785bool CodeEdit::is_indent_using_spaces() const {
786 return indent_using_spaces;
787}
788
789void CodeEdit::set_auto_indent_enabled(bool p_enabled) {
790 auto_indent = p_enabled;
791}
792
793bool CodeEdit::is_auto_indent_enabled() const {
794 return auto_indent;
795}
796
797void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
798 auto_indent_prefixes.clear();
799 for (int i = 0; i < p_prefixes.size(); i++) {
800 const String prefix = p_prefixes[i];
801 auto_indent_prefixes.insert(prefix[0]);
802 }
803}
804
805TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
806 TypedArray<String> prefixes;
807 for (const char32_t &E : auto_indent_prefixes) {
808 prefixes.push_back(String::chr(E));
809 }
810 return prefixes;
811}
812
813void CodeEdit::do_indent() {
814 if (!is_editable()) {
815 return;
816 }
817
818 if (has_selection()) {
819 indent_lines();
820 return;
821 }
822
823 if (!indent_using_spaces) {
824 insert_text_at_caret("\t");
825 return;
826 }
827
828 begin_complex_operation();
829 Vector<int> caret_edit_order = get_caret_index_edit_order();
830 for (const int &i : caret_edit_order) {
831 int spaces_to_add = _calculate_spaces_till_next_right_indent(get_caret_column(i));
832 if (spaces_to_add > 0) {
833 insert_text_at_caret(String(" ").repeat(spaces_to_add), i);
834 }
835 }
836 end_complex_operation();
837}
838
839void CodeEdit::indent_lines() {
840 if (!is_editable()) {
841 return;
842 }
843
844 begin_complex_operation();
845 Vector<int> caret_edit_order = get_caret_index_edit_order();
846 for (const int &c : caret_edit_order) {
847 // This value informs us by how much we changed selection position by indenting right.
848 // Default is 1 for tab indentation.
849 int selection_offset = 1;
850
851 int start_line = get_caret_line(c);
852 int end_line = start_line;
853 if (has_selection(c)) {
854 start_line = get_selection_from_line(c);
855 end_line = get_selection_to_line(c);
856
857 // Ignore the last line if the selection is not past the first column.
858 if (get_selection_to_column(c) == 0) {
859 selection_offset = 0;
860 end_line--;
861 }
862 }
863
864 for (int i = start_line; i <= end_line; i++) {
865 const String line_text = get_line(i);
866 if (line_text.size() == 0 && has_selection(c)) {
867 continue;
868 }
869
870 if (!indent_using_spaces) {
871 set_line(i, '\t' + line_text);
872 continue;
873 }
874
875 // We don't really care where selection is - we just need to know indentation level at the beginning of the line.
876 // Since we will add this many spaces, we want to move the whole selection and caret by this much.
877 int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
878 set_line(i, String(" ").repeat(spaces_to_add) + line_text);
879 selection_offset = spaces_to_add;
880 }
881
882 // Fix selection and caret being off after shifting selection right.
883 if (has_selection(c)) {
884 select(start_line, get_selection_from_column(c) + selection_offset, get_selection_to_line(c), get_selection_to_column(c) + selection_offset, c);
885 }
886 set_caret_column(get_caret_column(c) + selection_offset, false, c);
887 }
888 end_complex_operation();
889 queue_redraw();
890}
891
892void CodeEdit::unindent_lines() {
893 if (!is_editable()) {
894 return;
895 }
896
897 begin_complex_operation();
898
899 Vector<int> caret_edit_order = get_caret_index_edit_order();
900 for (const int &c : caret_edit_order) {
901 // Moving caret and selection after unindenting can get tricky because
902 // changing content of line can move caret and selection on its own (if new line ends before previous position of either)
903 // therefore we just remember initial values and at the end of the operation offset them by number of removed characters.
904 int removed_characters = 0;
905 int initial_selection_end_column = 0;
906 int initial_cursor_column = get_caret_column(c);
907
908 int start_line = get_caret_line(c);
909 int end_line = start_line;
910 if (has_selection(c)) {
911 start_line = get_selection_from_line(c);
912 end_line = get_selection_to_line(c);
913
914 // Ignore the last line if the selection is not past the first column.
915 initial_selection_end_column = get_selection_to_column(c);
916 if (initial_selection_end_column == 0) {
917 end_line--;
918 }
919 }
920
921 bool first_line_edited = false;
922 bool last_line_edited = false;
923
924 for (int i = start_line; i <= end_line; i++) {
925 String line_text = get_line(i);
926
927 if (line_text.begins_with("\t")) {
928 line_text = line_text.substr(1, line_text.length());
929
930 set_line(i, line_text);
931 removed_characters = 1;
932
933 first_line_edited = (i == start_line) ? true : first_line_edited;
934 last_line_edited = (i == end_line) ? true : last_line_edited;
935 continue;
936 }
937
938 if (line_text.begins_with(" ")) {
939 // When unindenting we aim to remove spaces before line that has selection no matter what is selected.
940 // Here we remove only enough spaces to align text to nearest full multiple of indentation_size.
941 // In case where selection begins at the start of indentation_size multiple we remove whole indentation level.
942 int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
943 line_text = line_text.substr(spaces_to_remove, line_text.length());
944
945 set_line(i, line_text);
946 removed_characters = spaces_to_remove;
947
948 first_line_edited = (i == start_line) ? true : first_line_edited;
949 last_line_edited = (i == end_line) ? true : last_line_edited;
950 }
951 }
952
953 if (has_selection(c)) {
954 // Fix selection being off by one on the first line.
955 if (first_line_edited) {
956 select(get_selection_from_line(c), get_selection_from_column(c) - removed_characters, get_selection_to_line(c), initial_selection_end_column, c);
957 }
958
959 // Fix selection being off by one on the last line.
960 if (last_line_edited) {
961 select(get_selection_from_line(c), get_selection_from_column(c), get_selection_to_line(c), initial_selection_end_column - removed_characters, c);
962 }
963 }
964 set_caret_column(initial_cursor_column - removed_characters, false, c);
965 }
966 end_complex_operation();
967 queue_redraw();
968}
969
970void CodeEdit::convert_indent(int p_from_line, int p_to_line) {
971 if (!is_editable()) {
972 return;
973 }
974
975 // Check line range.
976 p_from_line = (p_from_line < 0) ? 0 : p_from_line;
977 p_to_line = (p_to_line < 0) ? get_line_count() - 1 : p_to_line;
978
979 ERR_FAIL_COND(p_from_line >= get_line_count());
980 ERR_FAIL_COND(p_to_line >= get_line_count());
981 ERR_FAIL_COND(p_to_line < p_from_line);
982
983 // Store caret states.
984 Vector<int> caret_columns;
985 Vector<Pair<int, int>> from_selections;
986 Vector<Pair<int, int>> to_selections;
987 caret_columns.resize(get_caret_count());
988 from_selections.resize(get_caret_count());
989 to_selections.resize(get_caret_count());
990 for (int c = 0; c < get_caret_count(); c++) {
991 caret_columns.write[c] = get_caret_column(c);
992
993 // Set "selection_from_line" to -1 to allow checking if there was a selection later.
994 if (!has_selection(c)) {
995 from_selections.write[c].first = -1;
996 continue;
997 }
998 from_selections.write[c].first = get_selection_from_line(c);
999 from_selections.write[c].second = get_selection_from_column(c);
1000 to_selections.write[c].first = get_selection_to_line(c);
1001 to_selections.write[c].second = get_selection_to_column(c);
1002 }
1003
1004 // Check lines within range.
1005 const char32_t from_indent_char = indent_using_spaces ? '\t' : ' ';
1006 int size_diff = indent_using_spaces ? indent_size - 1 : -(indent_size - 1);
1007 bool changed_indentation = false;
1008 for (int i = p_from_line; i <= p_to_line; i++) {
1009 String line = get_line(i);
1010
1011 if (line.length() <= 0) {
1012 continue;
1013 }
1014
1015 // Check chars in the line.
1016 int j = 0;
1017 int space_count = 0;
1018 bool line_changed = false;
1019 while (j < line.length() && (line[j] == ' ' || line[j] == '\t')) {
1020 if (line[j] != from_indent_char) {
1021 space_count = 0;
1022 j++;
1023 continue;
1024 }
1025 space_count++;
1026
1027 if (!indent_using_spaces && space_count != indent_size) {
1028 j++;
1029 continue;
1030 }
1031
1032 line_changed = true;
1033 if (!changed_indentation) {
1034 begin_complex_operation();
1035 changed_indentation = true;
1036 }
1037
1038 // Calculate new caret state.
1039 for (int c = 0; c < get_caret_count(); c++) {
1040 if (get_caret_line(c) != i || caret_columns[c] <= j) {
1041 continue;
1042 }
1043 caret_columns.write[c] += size_diff;
1044
1045 if (from_selections.write[c].first == -1) {
1046 continue;
1047 }
1048 from_selections.write[c].second = from_selections[c].first == i ? from_selections[c].second + size_diff : from_selections[c].second;
1049 to_selections.write[c].second = to_selections[c].first == i ? to_selections[c].second + size_diff : to_selections[c].second;
1050 }
1051
1052 // Calculate new line.
1053 line = line.left(j + ((size_diff < 0) ? size_diff : 0)) + indent_text + line.substr(j + 1);
1054
1055 space_count = 0;
1056 j += size_diff;
1057 }
1058
1059 if (line_changed) {
1060 set_line(i, line);
1061 }
1062 }
1063
1064 if (!changed_indentation) {
1065 return;
1066 }
1067
1068 // Restore caret states.
1069 for (int c = 0; c < get_caret_count(); c++) {
1070 set_caret_column(caret_columns[c], c == 0, c);
1071 if (from_selections.write[c].first != -1) {
1072 select(from_selections.write[c].first, from_selections.write[c].second, to_selections.write[c].first, to_selections.write[c].second, c);
1073 }
1074 }
1075 merge_overlapping_carets();
1076 end_complex_operation();
1077 queue_redraw();
1078}
1079
1080int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
1081 int spaces_till_indent = p_column % indent_size;
1082 if (spaces_till_indent == 0) {
1083 spaces_till_indent = indent_size;
1084 }
1085 return spaces_till_indent;
1086}
1087
1088int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const {
1089 return indent_size - p_column % indent_size;
1090}
1091
1092void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
1093 if (!is_editable()) {
1094 return;
1095 }
1096
1097 begin_complex_operation();
1098 Vector<int> caret_edit_order = get_caret_index_edit_order();
1099 for (const int &i : caret_edit_order) {
1100 // When not splitting the line, we need to factor in indentation from the end of the current line.
1101 const int cc = p_split_current_line ? get_caret_column(i) : get_line(get_caret_line(i)).length();
1102 const int cl = get_caret_line(i);
1103
1104 const String line = get_line(cl);
1105
1106 String ins = "\n";
1107
1108 // Append current indentation.
1109 int space_count = 0;
1110 int line_col = 0;
1111 for (; line_col < cc; line_col++) {
1112 if (line[line_col] == '\t') {
1113 ins += indent_text;
1114 space_count = 0;
1115 continue;
1116 }
1117
1118 if (line[line_col] == ' ') {
1119 space_count++;
1120
1121 if (space_count == indent_size) {
1122 ins += indent_text;
1123 space_count = 0;
1124 }
1125 continue;
1126 }
1127 break;
1128 }
1129
1130 if (is_line_folded(cl)) {
1131 unfold_line(cl);
1132 }
1133
1134 // Indent once again if the previous line needs it, ie ':'.
1135 // Then add an addition new line for any closing pairs aka '()'.
1136 // Skip this in comments or if we are going above.
1137 bool brace_indent = false;
1138 if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
1139 bool should_indent = false;
1140 char32_t indent_char = ' ';
1141
1142 for (; line_col < cc; line_col++) {
1143 char32_t c = line[line_col];
1144 if (auto_indent_prefixes.has(c) && is_in_comment(cl, line_col) == -1) {
1145 should_indent = true;
1146 indent_char = c;
1147 continue;
1148 }
1149
1150 // Make sure this is the last char, trailing whitespace or comments are okay.
1151 // Increment column for comments because the delimiter (#) should be ignored.
1152 if (should_indent && (!is_whitespace(c) && is_in_comment(cl, line_col + 1) == -1)) {
1153 should_indent = false;
1154 }
1155 }
1156
1157 if (should_indent) {
1158 ins += indent_text;
1159
1160 String closing_pair = get_auto_brace_completion_close_key(String::chr(indent_char));
1161 if (!closing_pair.is_empty() && line.find(closing_pair, cc) == cc) {
1162 // No need to move the brace below if we are not taking the text with us.
1163 if (p_split_current_line) {
1164 brace_indent = true;
1165 ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2);
1166 } else {
1167 brace_indent = false;
1168 ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2);
1169 }
1170 }
1171 }
1172 }
1173
1174 bool first_line = false;
1175 if (!p_split_current_line) {
1176 deselect(i);
1177
1178 if (p_above) {
1179 if (cl > 0) {
1180 set_caret_line(cl - 1, false, true, 0, i);
1181 set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
1182 } else {
1183 set_caret_column(0, i == 0, i);
1184 first_line = true;
1185 }
1186 } else {
1187 set_caret_column(line.length(), i == 0, i);
1188 }
1189 }
1190
1191 insert_text_at_caret(ins, i);
1192
1193 if (first_line) {
1194 set_caret_line(0, i == 0, true, 0, i);
1195 } else if (brace_indent) {
1196 set_caret_line(get_caret_line(i) - 1, false, true, 0, i);
1197 set_caret_column(get_line(get_caret_line(i)).length(), i == 0, i);
1198 }
1199 }
1200
1201 end_complex_operation();
1202}
1203
1204/* Auto brace completion */
1205void CodeEdit::set_auto_brace_completion_enabled(bool p_enabled) {
1206 auto_brace_completion_enabled = p_enabled;
1207}
1208
1209bool CodeEdit::is_auto_brace_completion_enabled() const {
1210 return auto_brace_completion_enabled;
1211}
1212
1213void CodeEdit::set_highlight_matching_braces_enabled(bool p_enabled) {
1214 highlight_matching_braces_enabled = p_enabled;
1215 queue_redraw();
1216}
1217
1218bool CodeEdit::is_highlight_matching_braces_enabled() const {
1219 return highlight_matching_braces_enabled;
1220}
1221
1222void CodeEdit::add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key) {
1223 ERR_FAIL_COND_MSG(p_open_key.is_empty(), "auto brace completion open key cannot be empty");
1224 ERR_FAIL_COND_MSG(p_close_key.is_empty(), "auto brace completion close key cannot be empty");
1225
1226 for (int i = 0; i < p_open_key.length(); i++) {
1227 ERR_FAIL_COND_MSG(!is_symbol(p_open_key[i]), "auto brace completion open key must be a symbol");
1228 }
1229 for (int i = 0; i < p_close_key.length(); i++) {
1230 ERR_FAIL_COND_MSG(!is_symbol(p_close_key[i]), "auto brace completion close key must be a symbol");
1231 }
1232
1233 int at = 0;
1234 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
1235 ERR_FAIL_COND_MSG(auto_brace_completion_pairs[i].open_key == p_open_key, "auto brace completion open key '" + p_open_key + "' already exists.");
1236 if (p_open_key.length() < auto_brace_completion_pairs[i].open_key.length()) {
1237 at++;
1238 }
1239 }
1240
1241 BracePair brace_pair;
1242 brace_pair.open_key = p_open_key;
1243 brace_pair.close_key = p_close_key;
1244 auto_brace_completion_pairs.insert(at, brace_pair);
1245}
1246
1247void CodeEdit::set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs) {
1248 auto_brace_completion_pairs.clear();
1249
1250 Array keys = p_auto_brace_completion_pairs.keys();
1251 for (int i = 0; i < keys.size(); i++) {
1252 add_auto_brace_completion_pair(keys[i], p_auto_brace_completion_pairs[keys[i]]);
1253 }
1254}
1255
1256Dictionary CodeEdit::get_auto_brace_completion_pairs() const {
1257 Dictionary brace_pairs;
1258 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
1259 brace_pairs[auto_brace_completion_pairs[i].open_key] = auto_brace_completion_pairs[i].close_key;
1260 }
1261 return brace_pairs;
1262}
1263
1264bool CodeEdit::has_auto_brace_completion_open_key(const String &p_open_key) const {
1265 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
1266 if (auto_brace_completion_pairs[i].open_key == p_open_key) {
1267 return true;
1268 }
1269 }
1270 return false;
1271}
1272
1273bool CodeEdit::has_auto_brace_completion_close_key(const String &p_close_key) const {
1274 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
1275 if (auto_brace_completion_pairs[i].close_key == p_close_key) {
1276 return true;
1277 }
1278 }
1279 return false;
1280}
1281
1282String CodeEdit::get_auto_brace_completion_close_key(const String &p_open_key) const {
1283 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
1284 if (auto_brace_completion_pairs[i].open_key == p_open_key) {
1285 return auto_brace_completion_pairs[i].close_key;
1286 }
1287 }
1288 return String();
1289}
1290
1291/* Main Gutter */
1292void CodeEdit::_update_draw_main_gutter() {
1293 set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines);
1294}
1295
1296void CodeEdit::set_draw_breakpoints_gutter(bool p_draw) {
1297 draw_breakpoints = p_draw;
1298 set_gutter_clickable(main_gutter, p_draw);
1299 _update_draw_main_gutter();
1300}
1301
1302bool CodeEdit::is_drawing_breakpoints_gutter() const {
1303 return draw_breakpoints;
1304}
1305
1306void CodeEdit::set_draw_bookmarks_gutter(bool p_draw) {
1307 draw_bookmarks = p_draw;
1308 _update_draw_main_gutter();
1309}
1310
1311bool CodeEdit::is_drawing_bookmarks_gutter() const {
1312 return draw_bookmarks;
1313}
1314
1315void CodeEdit::set_draw_executing_lines_gutter(bool p_draw) {
1316 draw_executing_lines = p_draw;
1317 _update_draw_main_gutter();
1318}
1319
1320bool CodeEdit::is_drawing_executing_lines_gutter() const {
1321 return draw_executing_lines;
1322}
1323
1324void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
1325 if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) {
1326 bool breakpointed = is_line_breakpointed(p_line);
1327 bool hovering = p_region.has_point(get_local_mouse_pos());
1328 bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
1329
1330 if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) {
1331 int padding = p_region.size.x / 6;
1332
1333 Color use_color = theme_cache.breakpoint_color;
1334 if (hovering && !shift_pressed) {
1335 use_color = breakpointed ? use_color.lightened(0.3) : use_color.darkened(0.5);
1336 }
1337 Rect2 icon_region = p_region;
1338 icon_region.position += Point2(padding, padding);
1339 icon_region.size -= Point2(padding, padding) * 2;
1340 theme_cache.breakpoint_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
1341 }
1342 }
1343
1344 if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) {
1345 bool bookmarked = is_line_bookmarked(p_line);
1346 bool hovering = p_region.has_point(get_local_mouse_pos());
1347 bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
1348
1349 if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) {
1350 int horizontal_padding = p_region.size.x / 2;
1351 int vertical_padding = p_region.size.y / 4;
1352
1353 Color use_color = theme_cache.bookmark_color;
1354 if (hovering && shift_pressed) {
1355 use_color = bookmarked ? use_color.lightened(0.3) : use_color.darkened(0.5);
1356 }
1357 Rect2 icon_region = p_region;
1358 icon_region.position += Point2(horizontal_padding, 0);
1359 icon_region.size -= Point2(horizontal_padding * 1.1, vertical_padding);
1360 theme_cache.bookmark_icon->draw_rect(get_canvas_item(), icon_region, false, use_color);
1361 }
1362 }
1363
1364 if (draw_executing_lines && is_line_executing(p_line) && theme_cache.executing_line_icon.is_valid()) {
1365 int horizontal_padding = p_region.size.x / 10;
1366 int vertical_padding = p_region.size.y / 4;
1367
1368 Rect2 icon_region = p_region;
1369 icon_region.position += Point2(horizontal_padding, vertical_padding);
1370 icon_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
1371 theme_cache.executing_line_icon->draw_rect(get_canvas_item(), icon_region, false, theme_cache.executing_line_color);
1372 }
1373}
1374
1375// Breakpoints
1376void CodeEdit::set_line_as_breakpoint(int p_line, bool p_breakpointed) {
1377 ERR_FAIL_INDEX(p_line, get_line_count());
1378
1379 int mask = get_line_gutter_metadata(p_line, main_gutter);
1380 set_line_gutter_metadata(p_line, main_gutter, p_breakpointed ? mask | MAIN_GUTTER_BREAKPOINT : mask & ~MAIN_GUTTER_BREAKPOINT);
1381 if (p_breakpointed) {
1382 breakpointed_lines[p_line] = true;
1383 } else if (breakpointed_lines.has(p_line)) {
1384 breakpointed_lines.erase(p_line);
1385 }
1386 emit_signal(SNAME("breakpoint_toggled"), p_line);
1387 queue_redraw();
1388}
1389
1390bool CodeEdit::is_line_breakpointed(int p_line) const {
1391 return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_BREAKPOINT;
1392}
1393
1394void CodeEdit::clear_breakpointed_lines() {
1395 for (int i = 0; i < get_line_count(); i++) {
1396 if (is_line_breakpointed(i)) {
1397 set_line_as_breakpoint(i, false);
1398 }
1399 }
1400}
1401
1402PackedInt32Array CodeEdit::get_breakpointed_lines() const {
1403 PackedInt32Array ret;
1404 for (int i = 0; i < get_line_count(); i++) {
1405 if (is_line_breakpointed(i)) {
1406 ret.append(i);
1407 }
1408 }
1409 return ret;
1410}
1411
1412// Bookmarks
1413void CodeEdit::set_line_as_bookmarked(int p_line, bool p_bookmarked) {
1414 int mask = get_line_gutter_metadata(p_line, main_gutter);
1415 set_line_gutter_metadata(p_line, main_gutter, p_bookmarked ? mask | MAIN_GUTTER_BOOKMARK : mask & ~MAIN_GUTTER_BOOKMARK);
1416 queue_redraw();
1417}
1418
1419bool CodeEdit::is_line_bookmarked(int p_line) const {
1420 return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_BOOKMARK;
1421}
1422
1423void CodeEdit::clear_bookmarked_lines() {
1424 for (int i = 0; i < get_line_count(); i++) {
1425 if (is_line_bookmarked(i)) {
1426 set_line_as_bookmarked(i, false);
1427 }
1428 }
1429}
1430
1431PackedInt32Array CodeEdit::get_bookmarked_lines() const {
1432 PackedInt32Array ret;
1433 for (int i = 0; i < get_line_count(); i++) {
1434 if (is_line_bookmarked(i)) {
1435 ret.append(i);
1436 }
1437 }
1438 return ret;
1439}
1440
1441// Executing lines
1442void CodeEdit::set_line_as_executing(int p_line, bool p_executing) {
1443 int mask = get_line_gutter_metadata(p_line, main_gutter);
1444 set_line_gutter_metadata(p_line, main_gutter, p_executing ? mask | MAIN_GUTTER_EXECUTING : mask & ~MAIN_GUTTER_EXECUTING);
1445 queue_redraw();
1446}
1447
1448bool CodeEdit::is_line_executing(int p_line) const {
1449 return (int)get_line_gutter_metadata(p_line, main_gutter) & MAIN_GUTTER_EXECUTING;
1450}
1451
1452void CodeEdit::clear_executing_lines() {
1453 for (int i = 0; i < get_line_count(); i++) {
1454 if (is_line_executing(i)) {
1455 set_line_as_executing(i, false);
1456 }
1457 }
1458}
1459
1460PackedInt32Array CodeEdit::get_executing_lines() const {
1461 PackedInt32Array ret;
1462 for (int i = 0; i < get_line_count(); i++) {
1463 if (is_line_executing(i)) {
1464 ret.append(i);
1465 }
1466 }
1467 return ret;
1468}
1469
1470/* Line numbers */
1471void CodeEdit::set_draw_line_numbers(bool p_draw) {
1472 set_gutter_draw(line_number_gutter, p_draw);
1473}
1474
1475bool CodeEdit::is_draw_line_numbers_enabled() const {
1476 return is_gutter_drawn(line_number_gutter);
1477}
1478
1479void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) {
1480 p_zero_padded ? line_number_padding = "0" : line_number_padding = " ";
1481 queue_redraw();
1482}
1483
1484bool CodeEdit::is_line_numbers_zero_padded() const {
1485 return line_number_padding == "0";
1486}
1487
1488void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) {
1489 String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding);
1490 if (is_localizing_numeral_system()) {
1491 fc = TS->format_number(fc);
1492 }
1493 Ref<TextLine> tl;
1494 tl.instantiate();
1495 tl->add_string(fc, theme_cache.font, theme_cache.font_size);
1496 int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2;
1497 Color number_color = get_line_gutter_item_color(p_line, line_number_gutter);
1498 if (number_color == Color(1, 1, 1)) {
1499 number_color = theme_cache.line_number_color;
1500 }
1501 tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color);
1502}
1503
1504/* Fold Gutter */
1505void CodeEdit::set_draw_fold_gutter(bool p_draw) {
1506 set_gutter_draw(fold_gutter, p_draw);
1507}
1508
1509bool CodeEdit::is_drawing_fold_gutter() const {
1510 return is_gutter_drawn(fold_gutter);
1511}
1512
1513void CodeEdit::_fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region) {
1514 if (!can_fold_line(p_line) && !is_line_folded(p_line)) {
1515 set_line_gutter_clickable(p_line, fold_gutter, false);
1516 return;
1517 }
1518 set_line_gutter_clickable(p_line, fold_gutter, true);
1519
1520 int horizontal_padding = p_region.size.x / 10;
1521 int vertical_padding = p_region.size.y / 6;
1522
1523 p_region.position += Point2(horizontal_padding, vertical_padding);
1524 p_region.size -= Point2(horizontal_padding, vertical_padding) * 2;
1525
1526 bool can_fold = can_fold_line(p_line);
1527
1528 if (is_line_code_region_start(p_line)) {
1529 Color region_icon_color = theme_cache.folded_code_region_color;
1530 region_icon_color.a = MAX(region_icon_color.a, 0.4f);
1531 if (can_fold) {
1532 theme_cache.can_fold_code_region_icon->draw_rect(get_canvas_item(), p_region, false, region_icon_color);
1533 } else {
1534 theme_cache.folded_code_region_icon->draw_rect(get_canvas_item(), p_region, false, region_icon_color);
1535 }
1536 return;
1537 }
1538 if (can_fold) {
1539 theme_cache.can_fold_icon->draw_rect(get_canvas_item(), p_region, false, theme_cache.code_folding_color);
1540 return;
1541 }
1542 theme_cache.folded_icon->draw_rect(get_canvas_item(), p_region, false, theme_cache.code_folding_color);
1543}
1544
1545/* Line Folding */
1546void CodeEdit::set_line_folding_enabled(bool p_enabled) {
1547 line_folding_enabled = p_enabled;
1548 _set_hiding_enabled(p_enabled);
1549}
1550
1551bool CodeEdit::is_line_folding_enabled() const {
1552 return line_folding_enabled;
1553}
1554
1555bool CodeEdit::can_fold_line(int p_line) const {
1556 ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
1557 if (!line_folding_enabled) {
1558 return false;
1559 }
1560
1561 if (p_line + 1 >= get_line_count() || get_line(p_line).strip_edges().size() == 0) {
1562 return false;
1563 }
1564
1565 if (_is_line_hidden(p_line) || is_line_folded(p_line)) {
1566 return false;
1567 }
1568
1569 // Check for code region.
1570 if (is_line_code_region_end(p_line)) {
1571 return false;
1572 }
1573 if (is_line_code_region_start(p_line)) {
1574 int region_level = 0;
1575 // Check if there is a valid end region tag.
1576 for (int next_line = p_line + 1; next_line < get_line_count(); next_line++) {
1577 if (is_line_code_region_end(next_line)) {
1578 region_level -= 1;
1579 if (region_level == -1) {
1580 return true;
1581 }
1582 }
1583 if (is_line_code_region_start(next_line)) {
1584 region_level += 1;
1585 }
1586 }
1587 return false;
1588 }
1589
1590 /* Check for full multiline line or block strings / comments. */
1591 int in_comment = is_in_comment(p_line);
1592 int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
1593 if (in_string != -1 || in_comment != -1) {
1594 if (get_delimiter_start_position(p_line, get_line(p_line).size() - 1).y != p_line) {
1595 return false;
1596 }
1597
1598 int delimiter_end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
1599 /* No end line, therefore we have a multiline region over the rest of the file. */
1600 if (delimiter_end_line == -1) {
1601 return true;
1602 }
1603 /* End line is the same therefore we have a block. */
1604 if (delimiter_end_line == p_line) {
1605 /* Check we are the start of the block. */
1606 if (p_line - 1 >= 0) {
1607 if ((in_string != -1 && is_in_string(p_line - 1) != -1) || (in_comment != -1 && is_in_comment(p_line - 1) != -1)) {
1608 return false;
1609 }
1610 }
1611 /* Check it continues for at least one line. */
1612 return ((in_string != -1 && is_in_string(p_line + 1) != -1) || (in_comment != -1 && is_in_comment(p_line + 1) != -1));
1613 }
1614 return ((in_string != -1 && is_in_string(delimiter_end_line) != -1) || (in_comment != -1 && is_in_comment(delimiter_end_line) != -1));
1615 }
1616
1617 /* Otherwise check indent levels. */
1618 int start_indent = get_indent_level(p_line);
1619 for (int i = p_line + 1; i < get_line_count(); i++) {
1620 if (is_in_string(i) != -1 || is_in_comment(i) != -1 || get_line(i).strip_edges().size() == 0) {
1621 continue;
1622 }
1623 return (get_indent_level(i) > start_indent);
1624 }
1625 return false;
1626}
1627
1628void CodeEdit::fold_line(int p_line) {
1629 ERR_FAIL_INDEX(p_line, get_line_count());
1630 if (!is_line_folding_enabled() || !can_fold_line(p_line)) {
1631 return;
1632 }
1633
1634 /* Find the last line to be hidden. */
1635 const int line_count = get_line_count() - 1;
1636 int end_line = line_count;
1637
1638 // Fold code region.
1639 if (is_line_code_region_start(p_line)) {
1640 int region_level = 0;
1641 for (int endregion_line = p_line + 1; endregion_line < get_line_count(); endregion_line++) {
1642 if (is_line_code_region_start(endregion_line)) {
1643 region_level += 1;
1644 }
1645 if (is_line_code_region_end(endregion_line)) {
1646 region_level -= 1;
1647 if (region_level == -1) {
1648 end_line = endregion_line;
1649 break;
1650 }
1651 }
1652 }
1653 set_line_background_color(p_line, theme_cache.folded_code_region_color);
1654 }
1655
1656 int in_comment = is_in_comment(p_line);
1657 int in_string = (in_comment == -1) ? is_in_string(p_line) : -1;
1658 if (!is_line_code_region_start(p_line)) {
1659 if (in_string != -1 || in_comment != -1) {
1660 end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y;
1661 // End line is the same therefore we have a block of single line delimiters.
1662 if (end_line == p_line) {
1663 for (int i = p_line + 1; i <= line_count; i++) {
1664 if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) {
1665 break;
1666 }
1667 end_line = i;
1668 }
1669 }
1670 } else {
1671 int start_indent = get_indent_level(p_line);
1672 for (int i = p_line + 1; i <= line_count; i++) {
1673 if (get_line(i).strip_edges().size() == 0) {
1674 continue;
1675 }
1676 if (get_indent_level(i) > start_indent) {
1677 end_line = i;
1678 continue;
1679 }
1680 if (is_in_string(i) == -1 && is_in_comment(i) == -1) {
1681 break;
1682 }
1683 }
1684 }
1685 }
1686
1687 for (int i = p_line + 1; i <= end_line; i++) {
1688 _set_line_as_hidden(i, true);
1689 }
1690
1691 for (int i = 0; i < get_caret_count(); i++) {
1692 // Fix selection.
1693 if (has_selection(i)) {
1694 if (_is_line_hidden(get_selection_from_line(i)) && _is_line_hidden(get_selection_to_line(i))) {
1695 deselect(i);
1696 } else if (_is_line_hidden(get_selection_from_line(i))) {
1697 select(p_line, 9999, get_selection_to_line(i), get_selection_to_column(i), i);
1698 } else if (_is_line_hidden(get_selection_to_line(i))) {
1699 select(get_selection_from_line(i), get_selection_from_column(i), p_line, 9999, i);
1700 }
1701 }
1702
1703 // Reset caret.
1704 if (_is_line_hidden(get_caret_line(i))) {
1705 set_caret_line(p_line, false, false, 0, i);
1706 set_caret_column(get_line(p_line).length(), false, i);
1707 }
1708 }
1709
1710 merge_overlapping_carets();
1711 queue_redraw();
1712}
1713
1714void CodeEdit::unfold_line(int p_line) {
1715 ERR_FAIL_INDEX(p_line, get_line_count());
1716 if (!is_line_folded(p_line) && !_is_line_hidden(p_line)) {
1717 return;
1718 }
1719
1720 int fold_start = p_line;
1721 for (; fold_start > 0; fold_start--) {
1722 if (is_line_folded(fold_start)) {
1723 break;
1724 }
1725 }
1726 fold_start = is_line_folded(fold_start) ? fold_start : p_line;
1727
1728 for (int i = fold_start + 1; i < get_line_count(); i++) {
1729 if (!_is_line_hidden(i)) {
1730 break;
1731 }
1732 _set_line_as_hidden(i, false);
1733 if (is_line_code_region_start(i - 1)) {
1734 set_line_background_color(i - 1, Color(0.0, 0.0, 0.0, 0.0));
1735 }
1736 }
1737 queue_redraw();
1738}
1739
1740void CodeEdit::fold_all_lines() {
1741 for (int i = 0; i < get_line_count(); i++) {
1742 fold_line(i);
1743 }
1744 queue_redraw();
1745}
1746
1747void CodeEdit::unfold_all_lines() {
1748 _unhide_all_lines();
1749}
1750
1751void CodeEdit::toggle_foldable_line(int p_line) {
1752 ERR_FAIL_INDEX(p_line, get_line_count());
1753 if (is_line_folded(p_line)) {
1754 unfold_line(p_line);
1755 return;
1756 }
1757 fold_line(p_line);
1758}
1759
1760bool CodeEdit::is_line_folded(int p_line) const {
1761 ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
1762 return p_line + 1 < get_line_count() && !_is_line_hidden(p_line) && _is_line_hidden(p_line + 1);
1763}
1764
1765TypedArray<int> CodeEdit::get_folded_lines() const {
1766 TypedArray<int> folded_lines;
1767 for (int i = 0; i < get_line_count(); i++) {
1768 if (is_line_folded(i)) {
1769 folded_lines.push_back(i);
1770 }
1771 }
1772 return folded_lines;
1773}
1774
1775/* Code region */
1776void CodeEdit::create_code_region() {
1777 // Abort if there is no selected text.
1778 if (!has_selection()) {
1779 return;
1780 }
1781 // Check that region tag find a comment delimiter and is valid.
1782 if (code_region_start_string.is_empty()) {
1783 WARN_PRINT_ONCE("Cannot create code region without any one line comment delimiters");
1784 return;
1785 }
1786 begin_complex_operation();
1787 // Merge selections if selection starts on the same line the previous one ends.
1788 Vector<int> caret_edit_order = get_caret_index_edit_order();
1789 Vector<int> carets_to_remove;
1790 for (int i = 1; i < caret_edit_order.size(); i++) {
1791 int current_caret = caret_edit_order[i - 1];
1792 int next_caret = caret_edit_order[i];
1793 if (get_selection_from_line(current_caret) == get_selection_to_line(next_caret)) {
1794 select(get_selection_from_line(next_caret), get_selection_from_column(next_caret), get_selection_to_line(current_caret), get_selection_to_column(current_caret), next_caret);
1795 carets_to_remove.append(current_caret);
1796 }
1797 }
1798 // Sort and remove backwards to preserve indices.
1799 carets_to_remove.sort();
1800 for (int i = carets_to_remove.size() - 1; i >= 0; i--) {
1801 remove_caret(carets_to_remove[i]);
1802 }
1803
1804 // Adding start and end region tags.
1805 int first_region_start = -1;
1806 for (int caret_idx : get_caret_index_edit_order()) {
1807 if (!has_selection(caret_idx)) {
1808 continue;
1809 }
1810 int from_line = get_selection_from_line(caret_idx);
1811 if (first_region_start == -1 || from_line < first_region_start) {
1812 first_region_start = from_line;
1813 }
1814 int to_line = get_selection_to_line(caret_idx);
1815 set_line(to_line, get_line(to_line) + "\n" + code_region_end_string);
1816 insert_line_at(from_line, code_region_start_string + " " + RTR("New Code Region"));
1817 fold_line(from_line);
1818 }
1819
1820 // Select name of the first region to allow quick edit.
1821 remove_secondary_carets();
1822 set_caret_line(first_region_start);
1823 int tag_length = code_region_start_string.length() + RTR("New Code Region").length() + 1;
1824 set_caret_column(tag_length);
1825 select(first_region_start, code_region_start_string.length() + 1, first_region_start, tag_length);
1826
1827 end_complex_operation();
1828 queue_redraw();
1829}
1830
1831String CodeEdit::get_code_region_start_tag() const {
1832 return code_region_start_tag;
1833}
1834
1835String CodeEdit::get_code_region_end_tag() const {
1836 return code_region_end_tag;
1837}
1838
1839void CodeEdit::set_code_region_tags(const String &p_start, const String &p_end) {
1840 ERR_FAIL_COND_MSG(p_start == p_end, "Starting and ending region tags cannot be identical.");
1841 ERR_FAIL_COND_MSG(p_start.is_empty(), "Starting region tag cannot be empty.");
1842 ERR_FAIL_COND_MSG(p_end.is_empty(), "Ending region tag cannot be empty.");
1843 code_region_start_tag = p_start;
1844 code_region_end_tag = p_end;
1845 _update_code_region_tags();
1846}
1847
1848bool CodeEdit::is_line_code_region_start(int p_line) const {
1849 ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
1850 if (code_region_start_string.is_empty()) {
1851 return false;
1852 }
1853 return get_line(p_line).strip_edges().begins_with(code_region_start_string);
1854}
1855
1856bool CodeEdit::is_line_code_region_end(int p_line) const {
1857 ERR_FAIL_INDEX_V(p_line, get_line_count(), false);
1858 if (code_region_start_string.is_empty()) {
1859 return false;
1860 }
1861 return get_line(p_line).strip_edges().begins_with(code_region_end_string);
1862}
1863
1864/* Delimiters */
1865// Strings
1866void CodeEdit::add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) {
1867 _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_STRING);
1868}
1869
1870void CodeEdit::remove_string_delimiter(const String &p_start_key) {
1871 _remove_delimiter(p_start_key, TYPE_STRING);
1872}
1873
1874bool CodeEdit::has_string_delimiter(const String &p_start_key) const {
1875 return _has_delimiter(p_start_key, TYPE_STRING);
1876}
1877
1878void CodeEdit::set_string_delimiters(const TypedArray<String> &p_string_delimiters) {
1879 _set_delimiters(p_string_delimiters, TYPE_STRING);
1880}
1881
1882void CodeEdit::clear_string_delimiters() {
1883 _clear_delimiters(TYPE_STRING);
1884}
1885
1886TypedArray<String> CodeEdit::get_string_delimiters() const {
1887 return _get_delimiters(TYPE_STRING);
1888}
1889
1890int CodeEdit::is_in_string(int p_line, int p_column) const {
1891 return _is_in_delimiter(p_line, p_column, TYPE_STRING);
1892}
1893
1894// Comments
1895void CodeEdit::add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only) {
1896 _add_delimiter(p_start_key, p_end_key, p_line_only, TYPE_COMMENT);
1897}
1898
1899void CodeEdit::remove_comment_delimiter(const String &p_start_key) {
1900 _remove_delimiter(p_start_key, TYPE_COMMENT);
1901}
1902
1903bool CodeEdit::has_comment_delimiter(const String &p_start_key) const {
1904 return _has_delimiter(p_start_key, TYPE_COMMENT);
1905}
1906
1907void CodeEdit::set_comment_delimiters(const TypedArray<String> &p_comment_delimiters) {
1908 _set_delimiters(p_comment_delimiters, TYPE_COMMENT);
1909}
1910
1911void CodeEdit::clear_comment_delimiters() {
1912 _clear_delimiters(TYPE_COMMENT);
1913}
1914
1915TypedArray<String> CodeEdit::get_comment_delimiters() const {
1916 return _get_delimiters(TYPE_COMMENT);
1917}
1918
1919int CodeEdit::is_in_comment(int p_line, int p_column) const {
1920 return _is_in_delimiter(p_line, p_column, TYPE_COMMENT);
1921}
1922
1923String CodeEdit::get_delimiter_start_key(int p_delimiter_idx) const {
1924 ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), "");
1925 return delimiters[p_delimiter_idx].start_key;
1926}
1927
1928String CodeEdit::get_delimiter_end_key(int p_delimiter_idx) const {
1929 ERR_FAIL_INDEX_V(p_delimiter_idx, delimiters.size(), "");
1930 return delimiters[p_delimiter_idx].end_key;
1931}
1932
1933Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const {
1934 if (delimiters.size() == 0) {
1935 return Point2(-1, -1);
1936 }
1937 ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
1938 ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1));
1939
1940 Point2 start_position;
1941 start_position.y = -1;
1942 start_position.x = -1;
1943
1944 bool in_region = ((p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->get()) != -1;
1945
1946 /* Check the keys for this line. */
1947 for (const KeyValue<int, int> &E : delimiter_cache[p_line]) {
1948 if (E.key > p_column) {
1949 break;
1950 }
1951 in_region = E.value != -1;
1952 start_position.x = in_region ? E.key : -1;
1953 }
1954
1955 /* Region was found on this line and is not a multiline continuation. */
1956 int line_length = get_line(p_line).length();
1957 if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) {
1958 start_position.y = p_line;
1959 return start_position;
1960 }
1961
1962 /* Not in a region */
1963 if (!in_region) {
1964 return start_position;
1965 }
1966
1967 /* Region starts on a previous line */
1968 for (int i = p_line - 1; i >= 0; i--) {
1969 if (delimiter_cache[i].size() < 1) {
1970 continue;
1971 }
1972 start_position.y = i;
1973 start_position.x = delimiter_cache[i].back()->key();
1974
1975 /* Make sure it's not a multiline continuation. */
1976 line_length = get_line(i).length();
1977 if (line_length > 0 && start_position.x != line_length + 1) {
1978 break;
1979 }
1980 }
1981 return start_position;
1982}
1983
1984Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const {
1985 if (delimiters.size() == 0) {
1986 return Point2(-1, -1);
1987 }
1988 ERR_FAIL_INDEX_V(p_line, get_line_count(), Point2(-1, -1));
1989 ERR_FAIL_COND_V(p_column - 1 > get_line(p_line).size(), Point2(-1, -1));
1990
1991 Point2 end_position;
1992 end_position.y = -1;
1993 end_position.x = -1;
1994
1995 int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
1996
1997 /* Check the keys for this line. */
1998 for (const KeyValue<int, int> &E : delimiter_cache[p_line]) {
1999 end_position.x = (E.value == -1) ? E.key : -1;
2000 if (E.key > p_column) {
2001 break;
2002 }
2003 region = E.value;
2004 }
2005
2006 /* Region was found on this line and is not a multiline continuation. */
2007 if (region != -1 && end_position.x != -1 && (delimiters[region].line_only || end_position.x != get_line(p_line).length() + 1)) {
2008 end_position.y = p_line;
2009 return end_position;
2010 }
2011
2012 /* Not in a region */
2013 if (region == -1) {
2014 end_position.x = -1;
2015 return end_position;
2016 }
2017
2018 /* Region ends on a later line */
2019 for (int i = p_line + 1; i < get_line_count(); i++) {
2020 if (delimiter_cache[i].size() < 1 || delimiter_cache[i].front()->value() != -1) {
2021 continue;
2022 }
2023 end_position.x = delimiter_cache[i].front()->key();
2024
2025 /* Make sure it's not a multiline continuation. */
2026 if (get_line(i).length() > 0 && end_position.x != get_line(i).length() + 1) {
2027 end_position.y = i;
2028 break;
2029 }
2030 end_position.x = -1;
2031 }
2032 return end_position;
2033}
2034
2035/* Code hint */
2036void CodeEdit::set_code_hint(const String &p_hint) {
2037 code_hint = p_hint;
2038 code_hint_xpos = -0xFFFF;
2039 queue_redraw();
2040}
2041
2042void CodeEdit::set_code_hint_draw_below(bool p_below) {
2043 code_hint_draw_below = p_below;
2044 queue_redraw();
2045}
2046
2047/* Code Completion */
2048void CodeEdit::set_code_completion_enabled(bool p_enable) {
2049 code_completion_enabled = p_enable;
2050}
2051
2052bool CodeEdit::is_code_completion_enabled() const {
2053 return code_completion_enabled;
2054}
2055
2056void CodeEdit::set_code_completion_prefixes(const TypedArray<String> &p_prefixes) {
2057 code_completion_prefixes.clear();
2058 for (int i = 0; i < p_prefixes.size(); i++) {
2059 const String prefix = p_prefixes[i];
2060
2061 ERR_CONTINUE_MSG(prefix.is_empty(), "Code completion prefix cannot be empty.");
2062 code_completion_prefixes.insert(prefix[0]);
2063 }
2064}
2065
2066TypedArray<String> CodeEdit::get_code_completion_prefixes() const {
2067 TypedArray<String> prefixes;
2068 for (const char32_t &E : code_completion_prefixes) {
2069 prefixes.push_back(String::chr(E));
2070 }
2071 return prefixes;
2072}
2073
2074String CodeEdit::get_text_for_code_completion() const {
2075 StringBuilder completion_text;
2076 const int text_size = get_line_count();
2077 for (int i = 0; i < text_size; i++) {
2078 String line = get_line(i);
2079
2080 if (i == get_caret_line()) {
2081 completion_text += line.substr(0, get_caret_column());
2082 /* Not unicode, represents the caret. */
2083 completion_text += String::chr(0xFFFF);
2084 completion_text += line.substr(get_caret_column(), line.size());
2085 } else {
2086 completion_text += line;
2087 }
2088
2089 if (i != text_size - 1) {
2090 completion_text += "\n";
2091 }
2092 }
2093 return completion_text.as_string();
2094}
2095
2096void CodeEdit::request_code_completion(bool p_force) {
2097 if (GDVIRTUAL_CALL(_request_code_completion, p_force)) {
2098 return;
2099 }
2100
2101 /* Don't re-query if all existing options are quoted types, eg path, signal. */
2102 bool ignored = code_completion_active && !code_completion_options.is_empty();
2103 if (ignored) {
2104 ScriptLanguage::CodeCompletionKind kind = ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT;
2105 const ScriptLanguage::CodeCompletionOption *previous_option = nullptr;
2106 for (int i = 0; i < code_completion_options.size(); i++) {
2107 const ScriptLanguage::CodeCompletionOption &current_option = code_completion_options[i];
2108 if (!previous_option) {
2109 previous_option = &current_option;
2110 kind = current_option.kind;
2111 }
2112 if (previous_option->kind != current_option.kind) {
2113 ignored = false;
2114 break;
2115 }
2116 }
2117 ignored = ignored && (kind == ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH || kind == ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL);
2118 }
2119
2120 if (ignored) {
2121 return;
2122 }
2123
2124 if (p_force) {
2125 emit_signal(SNAME("code_completion_requested"));
2126 return;
2127 }
2128
2129 String line = get_line(get_caret_line());
2130 int ofs = CLAMP(get_caret_column(), 0, line.length());
2131
2132 if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || !is_symbol(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) {
2133 emit_signal(SNAME("code_completion_requested"));
2134 } else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) {
2135 emit_signal(SNAME("code_completion_requested"));
2136 }
2137}
2138
2139void CodeEdit::add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value, int p_location) {
2140 ScriptLanguage::CodeCompletionOption completion_option;
2141 completion_option.kind = (ScriptLanguage::CodeCompletionKind)p_type;
2142 completion_option.display = p_display_text;
2143 completion_option.insert_text = p_insert_text;
2144 completion_option.font_color = p_text_color;
2145 completion_option.icon = p_icon;
2146 completion_option.default_value = p_value;
2147 completion_option.location = p_location;
2148 code_completion_option_submitted.push_back(completion_option);
2149}
2150
2151void CodeEdit::update_code_completion_options(bool p_forced) {
2152 code_completion_forced = p_forced;
2153 code_completion_option_sources = code_completion_option_submitted;
2154 code_completion_option_submitted.clear();
2155 _filter_code_completion_candidates_impl();
2156}
2157
2158TypedArray<Dictionary> CodeEdit::get_code_completion_options() const {
2159 if (!code_completion_active) {
2160 return TypedArray<Dictionary>();
2161 }
2162
2163 TypedArray<Dictionary> completion_options;
2164 completion_options.resize(code_completion_options.size());
2165 for (int i = 0; i < code_completion_options.size(); i++) {
2166 Dictionary option;
2167 option["kind"] = code_completion_options[i].kind;
2168 option["display_text"] = code_completion_options[i].display;
2169 option["insert_text"] = code_completion_options[i].insert_text;
2170 option["font_color"] = code_completion_options[i].font_color;
2171 option["icon"] = code_completion_options[i].icon;
2172 option["location"] = code_completion_options[i].location;
2173 option["default_value"] = code_completion_options[i].default_value;
2174 completion_options[i] = option;
2175 }
2176 return completion_options;
2177}
2178
2179Dictionary CodeEdit::get_code_completion_option(int p_index) const {
2180 if (!code_completion_active) {
2181 return Dictionary();
2182 }
2183 ERR_FAIL_INDEX_V(p_index, code_completion_options.size(), Dictionary());
2184
2185 Dictionary option;
2186 option["kind"] = code_completion_options[p_index].kind;
2187 option["display_text"] = code_completion_options[p_index].display;
2188 option["insert_text"] = code_completion_options[p_index].insert_text;
2189 option["font_color"] = code_completion_options[p_index].font_color;
2190 option["icon"] = code_completion_options[p_index].icon;
2191 option["location"] = code_completion_options[p_index].location;
2192 option["default_value"] = code_completion_options[p_index].default_value;
2193 return option;
2194}
2195
2196int CodeEdit::get_code_completion_selected_index() const {
2197 return (code_completion_active) ? code_completion_current_selected : -1;
2198}
2199
2200void CodeEdit::set_code_completion_selected_index(int p_index) {
2201 if (!code_completion_active) {
2202 return;
2203 }
2204 ERR_FAIL_INDEX(p_index, code_completion_options.size());
2205 code_completion_current_selected = p_index;
2206 code_completion_force_item_center = -1;
2207 queue_redraw();
2208}
2209
2210void CodeEdit::confirm_code_completion(bool p_replace) {
2211 if (!is_editable() || !code_completion_active) {
2212 return;
2213 }
2214
2215 if (GDVIRTUAL_CALL(_confirm_code_completion, p_replace)) {
2216 return;
2217 }
2218
2219 char32_t caret_last_completion_char = 0;
2220 begin_complex_operation();
2221 Vector<int> caret_edit_order = get_caret_index_edit_order();
2222 for (const int &i : caret_edit_order) {
2223 int caret_line = get_caret_line(i);
2224
2225 const String &insert_text = code_completion_options[code_completion_current_selected].insert_text;
2226 const String &display_text = code_completion_options[code_completion_current_selected].display;
2227
2228 if (p_replace) {
2229 // Find end of current section.
2230 const String line = get_line(caret_line);
2231 int caret_col = get_caret_column(i);
2232 int caret_remove_line = caret_line;
2233
2234 bool merge_text = true;
2235 int in_string = is_in_string(caret_line, caret_col);
2236 if (in_string != -1) {
2237 Point2 string_end = get_delimiter_end_position(caret_line, caret_col);
2238 if (string_end.x != -1) {
2239 merge_text = false;
2240 caret_remove_line = string_end.y;
2241 caret_col = string_end.x - 1;
2242 }
2243 }
2244
2245 if (merge_text) {
2246 for (; caret_col < line.length(); caret_col++) {
2247 if (is_symbol(line[caret_col])) {
2248 break;
2249 }
2250 }
2251 }
2252
2253 // Replace.
2254 remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_remove_line, caret_col);
2255 adjust_carets_after_edit(i, caret_line, caret_col - code_completion_base.length(), caret_remove_line, caret_col);
2256 set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
2257 insert_text_at_caret(insert_text, i);
2258 } else {
2259 // Get first non-matching char.
2260 const String line = get_line(caret_line);
2261 int caret_col = get_caret_column(i);
2262 int matching_chars = code_completion_base.length();
2263 for (; matching_chars <= insert_text.length(); matching_chars++) {
2264 if (caret_col >= line.length() || line[caret_col] != insert_text[matching_chars]) {
2265 break;
2266 }
2267 caret_col++;
2268 }
2269
2270 // Remove base completion text.
2271 remove_text(caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
2272 adjust_carets_after_edit(i, caret_line, get_caret_column(i) - code_completion_base.length(), caret_line, get_caret_column(i));
2273 set_caret_column(get_caret_column(i) - code_completion_base.length(), false, i);
2274
2275 // Merge with text.
2276 insert_text_at_caret(insert_text.substr(0, code_completion_base.length()), i);
2277 set_caret_column(caret_col, false, i);
2278 insert_text_at_caret(insert_text.substr(matching_chars), i);
2279 }
2280
2281 // Handle merging of symbols eg strings, brackets.
2282 const String line = get_line(caret_line);
2283 char32_t next_char = line[get_caret_column(i)];
2284 char32_t last_completion_char = insert_text[insert_text.length() - 1];
2285 if (i == 0) {
2286 caret_last_completion_char = last_completion_char;
2287 }
2288 char32_t last_completion_char_display = display_text[display_text.length() - 1];
2289
2290 bool last_char_matches = (last_completion_char == next_char || last_completion_char_display == next_char);
2291 int pre_brace_pair = get_caret_column(i) > 0 ? _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i)) : -1;
2292 int post_brace_pair = get_caret_column(i) < get_line(caret_line).length() ? _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i)) : -1;
2293
2294 // Strings do not nest like brackets, so ensure we don't add an additional closing pair.
2295 if (has_string_delimiter(String::chr(last_completion_char))) {
2296 if (post_brace_pair != -1 && last_char_matches) {
2297 remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
2298 adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
2299 }
2300 } else {
2301 if (pre_brace_pair != -1 && pre_brace_pair != post_brace_pair && last_char_matches) {
2302 remove_text(caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
2303 adjust_carets_after_edit(i, caret_line, get_caret_column(i), caret_line, get_caret_column(i) + 1);
2304 } else if (auto_brace_completion_enabled && pre_brace_pair != -1) {
2305 insert_text_at_caret(auto_brace_completion_pairs[pre_brace_pair].close_key, i);
2306 set_caret_column(get_caret_column(i) - auto_brace_completion_pairs[pre_brace_pair].close_key.length(), i == 0, i);
2307 }
2308 }
2309
2310 if (pre_brace_pair == -1 && post_brace_pair == -1 && get_caret_column(i) > 0 && get_caret_column(i) < get_line(caret_line).length()) {
2311 pre_brace_pair = _get_auto_brace_pair_open_at_pos(caret_line, get_caret_column(i) + 1);
2312 if (pre_brace_pair != -1 && pre_brace_pair == _get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1)) {
2313 remove_text(caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
2314 adjust_carets_after_edit(i, caret_line, get_caret_column(i) - 2, caret_line, get_caret_column(i));
2315 if (_get_auto_brace_pair_close_at_pos(caret_line, get_caret_column(i) - 1) != pre_brace_pair) {
2316 set_caret_column(get_caret_column(i) - 1, i == 0, i);
2317 }
2318 }
2319 }
2320 }
2321 end_complex_operation();
2322
2323 cancel_code_completion();
2324 if (code_completion_prefixes.has(caret_last_completion_char)) {
2325 request_code_completion();
2326 }
2327}
2328
2329void CodeEdit::cancel_code_completion() {
2330 if (!code_completion_active) {
2331 return;
2332 }
2333 code_completion_forced = false;
2334 code_completion_active = false;
2335 is_code_completion_drag_started = false;
2336 queue_redraw();
2337}
2338
2339/* Line length guidelines */
2340void CodeEdit::set_line_length_guidelines(TypedArray<int> p_guideline_columns) {
2341 line_length_guideline_columns = p_guideline_columns;
2342 queue_redraw();
2343}
2344
2345TypedArray<int> CodeEdit::get_line_length_guidelines() const {
2346 return line_length_guideline_columns;
2347}
2348
2349/* Symbol lookup */
2350void CodeEdit::set_symbol_lookup_on_click_enabled(bool p_enabled) {
2351 symbol_lookup_on_click_enabled = p_enabled;
2352 set_symbol_lookup_word_as_valid(false);
2353}
2354
2355bool CodeEdit::is_symbol_lookup_on_click_enabled() const {
2356 return symbol_lookup_on_click_enabled;
2357}
2358
2359String CodeEdit::get_text_for_symbol_lookup() const {
2360 Point2i mp = get_local_mouse_pos();
2361 Point2i pos = get_line_column_at_pos(mp, false);
2362 int line = pos.y;
2363 int col = pos.x;
2364
2365 if (line == -1) {
2366 return String();
2367 }
2368
2369 return get_text_with_cursor_char(line, col);
2370}
2371
2372String CodeEdit::get_text_with_cursor_char(int p_line, int p_column) const {
2373 const int text_size = get_line_count();
2374 StringBuilder result;
2375 for (int i = 0; i < text_size; i++) {
2376 String line_text = get_line(i);
2377 if (i == p_line && p_column >= 0 && p_column <= line_text.size()) {
2378 result += line_text.substr(0, p_column);
2379 /* Not unicode, represents the cursor. */
2380 result += String::chr(0xFFFF);
2381 result += line_text.substr(p_column, line_text.size());
2382 } else {
2383 result += line_text;
2384 }
2385
2386 if (i != text_size - 1) {
2387 result += "\n";
2388 }
2389 }
2390
2391 return result.as_string();
2392}
2393
2394void CodeEdit::set_symbol_lookup_word_as_valid(bool p_valid) {
2395 symbol_lookup_word = p_valid ? symbol_lookup_new_word : "";
2396 symbol_lookup_new_word = "";
2397 if (lookup_symbol_word != symbol_lookup_word) {
2398 _set_symbol_lookup_word(symbol_lookup_word);
2399 }
2400}
2401
2402/* Visual */
2403Color CodeEdit::_get_brace_mismatch_color() const {
2404 return theme_cache.brace_mismatch_color;
2405}
2406
2407Color CodeEdit::_get_code_folding_color() const {
2408 return theme_cache.code_folding_color;
2409}
2410
2411Ref<Texture2D> CodeEdit::_get_folded_eol_icon() const {
2412 return theme_cache.folded_eol_icon;
2413}
2414
2415void CodeEdit::_bind_methods() {
2416 /* Indent management */
2417 ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size);
2418 ClassDB::bind_method(D_METHOD("get_indent_size"), &CodeEdit::get_indent_size);
2419
2420 ClassDB::bind_method(D_METHOD("set_indent_using_spaces", "use_spaces"), &CodeEdit::set_indent_using_spaces);
2421 ClassDB::bind_method(D_METHOD("is_indent_using_spaces"), &CodeEdit::is_indent_using_spaces);
2422
2423 ClassDB::bind_method(D_METHOD("set_auto_indent_enabled", "enable"), &CodeEdit::set_auto_indent_enabled);
2424 ClassDB::bind_method(D_METHOD("is_auto_indent_enabled"), &CodeEdit::is_auto_indent_enabled);
2425
2426 ClassDB::bind_method(D_METHOD("set_auto_indent_prefixes", "prefixes"), &CodeEdit::set_auto_indent_prefixes);
2427 ClassDB::bind_method(D_METHOD("get_auto_indent_prefixes"), &CodeEdit::get_auto_indent_prefixes);
2428
2429 ClassDB::bind_method(D_METHOD("do_indent"), &CodeEdit::do_indent);
2430
2431 ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines);
2432 ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines);
2433
2434 ClassDB::bind_method(D_METHOD("convert_indent", "from_line", "to_line"), &CodeEdit::convert_indent, DEFVAL(-1), DEFVAL(-1));
2435
2436 /* Auto brace completion */
2437 ClassDB::bind_method(D_METHOD("set_auto_brace_completion_enabled", "enable"), &CodeEdit::set_auto_brace_completion_enabled);
2438 ClassDB::bind_method(D_METHOD("is_auto_brace_completion_enabled"), &CodeEdit::is_auto_brace_completion_enabled);
2439
2440 ClassDB::bind_method(D_METHOD("set_highlight_matching_braces_enabled", "enable"), &CodeEdit::set_highlight_matching_braces_enabled);
2441 ClassDB::bind_method(D_METHOD("is_highlight_matching_braces_enabled"), &CodeEdit::is_highlight_matching_braces_enabled);
2442
2443 ClassDB::bind_method(D_METHOD("add_auto_brace_completion_pair", "start_key", "end_key"), &CodeEdit::add_auto_brace_completion_pair);
2444 ClassDB::bind_method(D_METHOD("set_auto_brace_completion_pairs", "pairs"), &CodeEdit::set_auto_brace_completion_pairs);
2445 ClassDB::bind_method(D_METHOD("get_auto_brace_completion_pairs"), &CodeEdit::get_auto_brace_completion_pairs);
2446
2447 ClassDB::bind_method(D_METHOD("has_auto_brace_completion_open_key", "open_key"), &CodeEdit::has_auto_brace_completion_open_key);
2448 ClassDB::bind_method(D_METHOD("has_auto_brace_completion_close_key", "close_key"), &CodeEdit::has_auto_brace_completion_close_key);
2449
2450 ClassDB::bind_method(D_METHOD("get_auto_brace_completion_close_key", "open_key"), &CodeEdit::get_auto_brace_completion_close_key);
2451
2452 /* Main Gutter */
2453 ClassDB::bind_method(D_METHOD("set_draw_breakpoints_gutter", "enable"), &CodeEdit::set_draw_breakpoints_gutter);
2454 ClassDB::bind_method(D_METHOD("is_drawing_breakpoints_gutter"), &CodeEdit::is_drawing_breakpoints_gutter);
2455
2456 ClassDB::bind_method(D_METHOD("set_draw_bookmarks_gutter", "enable"), &CodeEdit::set_draw_bookmarks_gutter);
2457 ClassDB::bind_method(D_METHOD("is_drawing_bookmarks_gutter"), &CodeEdit::is_drawing_bookmarks_gutter);
2458
2459 ClassDB::bind_method(D_METHOD("set_draw_executing_lines_gutter", "enable"), &CodeEdit::set_draw_executing_lines_gutter);
2460 ClassDB::bind_method(D_METHOD("is_drawing_executing_lines_gutter"), &CodeEdit::is_drawing_executing_lines_gutter);
2461
2462 // Breakpoints
2463 ClassDB::bind_method(D_METHOD("set_line_as_breakpoint", "line", "breakpointed"), &CodeEdit::set_line_as_breakpoint);
2464 ClassDB::bind_method(D_METHOD("is_line_breakpointed", "line"), &CodeEdit::is_line_breakpointed);
2465 ClassDB::bind_method(D_METHOD("clear_breakpointed_lines"), &CodeEdit::clear_breakpointed_lines);
2466 ClassDB::bind_method(D_METHOD("get_breakpointed_lines"), &CodeEdit::get_breakpointed_lines);
2467
2468 // Bookmarks
2469 ClassDB::bind_method(D_METHOD("set_line_as_bookmarked", "line", "bookmarked"), &CodeEdit::set_line_as_bookmarked);
2470 ClassDB::bind_method(D_METHOD("is_line_bookmarked", "line"), &CodeEdit::is_line_bookmarked);
2471 ClassDB::bind_method(D_METHOD("clear_bookmarked_lines"), &CodeEdit::clear_bookmarked_lines);
2472 ClassDB::bind_method(D_METHOD("get_bookmarked_lines"), &CodeEdit::get_bookmarked_lines);
2473
2474 // Executing lines
2475 ClassDB::bind_method(D_METHOD("set_line_as_executing", "line", "executing"), &CodeEdit::set_line_as_executing);
2476 ClassDB::bind_method(D_METHOD("is_line_executing", "line"), &CodeEdit::is_line_executing);
2477 ClassDB::bind_method(D_METHOD("clear_executing_lines"), &CodeEdit::clear_executing_lines);
2478 ClassDB::bind_method(D_METHOD("get_executing_lines"), &CodeEdit::get_executing_lines);
2479
2480 /* Line numbers */
2481 ClassDB::bind_method(D_METHOD("set_draw_line_numbers", "enable"), &CodeEdit::set_draw_line_numbers);
2482 ClassDB::bind_method(D_METHOD("is_draw_line_numbers_enabled"), &CodeEdit::is_draw_line_numbers_enabled);
2483 ClassDB::bind_method(D_METHOD("set_line_numbers_zero_padded", "enable"), &CodeEdit::set_line_numbers_zero_padded);
2484 ClassDB::bind_method(D_METHOD("is_line_numbers_zero_padded"), &CodeEdit::is_line_numbers_zero_padded);
2485
2486 /* Fold Gutter */
2487 ClassDB::bind_method(D_METHOD("set_draw_fold_gutter", "enable"), &CodeEdit::set_draw_fold_gutter);
2488 ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &CodeEdit::is_drawing_fold_gutter);
2489
2490 /* Line folding */
2491 ClassDB::bind_method(D_METHOD("set_line_folding_enabled", "enabled"), &CodeEdit::set_line_folding_enabled);
2492 ClassDB::bind_method(D_METHOD("is_line_folding_enabled"), &CodeEdit::is_line_folding_enabled);
2493
2494 ClassDB::bind_method(D_METHOD("can_fold_line", "line"), &CodeEdit::can_fold_line);
2495
2496 ClassDB::bind_method(D_METHOD("fold_line", "line"), &CodeEdit::fold_line);
2497 ClassDB::bind_method(D_METHOD("unfold_line", "line"), &CodeEdit::unfold_line);
2498 ClassDB::bind_method(D_METHOD("fold_all_lines"), &CodeEdit::fold_all_lines);
2499 ClassDB::bind_method(D_METHOD("unfold_all_lines"), &CodeEdit::unfold_all_lines);
2500 ClassDB::bind_method(D_METHOD("toggle_foldable_line", "line"), &CodeEdit::toggle_foldable_line);
2501
2502 ClassDB::bind_method(D_METHOD("is_line_folded", "line"), &CodeEdit::is_line_folded);
2503 ClassDB::bind_method(D_METHOD("get_folded_lines"), &CodeEdit::get_folded_lines);
2504
2505 /* Code region */
2506 ClassDB::bind_method(D_METHOD("create_code_region"), &CodeEdit::create_code_region);
2507 ClassDB::bind_method(D_METHOD("get_code_region_start_tag"), &CodeEdit::get_code_region_start_tag);
2508 ClassDB::bind_method(D_METHOD("get_code_region_end_tag"), &CodeEdit::get_code_region_end_tag);
2509 ClassDB::bind_method(D_METHOD("set_code_region_tags", "start", "end"), &CodeEdit::set_code_region_tags, DEFVAL("region"), DEFVAL("endregion"));
2510 ClassDB::bind_method(D_METHOD("is_line_code_region_start", "line"), &CodeEdit::is_line_code_region_start);
2511 ClassDB::bind_method(D_METHOD("is_line_code_region_end", "line"), &CodeEdit::is_line_code_region_end);
2512
2513 /* Delimiters */
2514 // Strings
2515 ClassDB::bind_method(D_METHOD("add_string_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_string_delimiter, DEFVAL(false));
2516 ClassDB::bind_method(D_METHOD("remove_string_delimiter", "start_key"), &CodeEdit::remove_string_delimiter);
2517 ClassDB::bind_method(D_METHOD("has_string_delimiter", "start_key"), &CodeEdit::has_string_delimiter);
2518
2519 ClassDB::bind_method(D_METHOD("set_string_delimiters", "string_delimiters"), &CodeEdit::set_string_delimiters);
2520 ClassDB::bind_method(D_METHOD("clear_string_delimiters"), &CodeEdit::clear_string_delimiters);
2521 ClassDB::bind_method(D_METHOD("get_string_delimiters"), &CodeEdit::get_string_delimiters);
2522
2523 ClassDB::bind_method(D_METHOD("is_in_string", "line", "column"), &CodeEdit::is_in_string, DEFVAL(-1));
2524
2525 // Comments
2526 ClassDB::bind_method(D_METHOD("add_comment_delimiter", "start_key", "end_key", "line_only"), &CodeEdit::add_comment_delimiter, DEFVAL(false));
2527 ClassDB::bind_method(D_METHOD("remove_comment_delimiter", "start_key"), &CodeEdit::remove_comment_delimiter);
2528 ClassDB::bind_method(D_METHOD("has_comment_delimiter", "start_key"), &CodeEdit::has_comment_delimiter);
2529
2530 ClassDB::bind_method(D_METHOD("set_comment_delimiters", "comment_delimiters"), &CodeEdit::set_comment_delimiters);
2531 ClassDB::bind_method(D_METHOD("clear_comment_delimiters"), &CodeEdit::clear_comment_delimiters);
2532 ClassDB::bind_method(D_METHOD("get_comment_delimiters"), &CodeEdit::get_comment_delimiters);
2533
2534 ClassDB::bind_method(D_METHOD("is_in_comment", "line", "column"), &CodeEdit::is_in_comment, DEFVAL(-1));
2535
2536 // Util
2537 ClassDB::bind_method(D_METHOD("get_delimiter_start_key", "delimiter_index"), &CodeEdit::get_delimiter_start_key);
2538 ClassDB::bind_method(D_METHOD("get_delimiter_end_key", "delimiter_index"), &CodeEdit::get_delimiter_end_key);
2539
2540 ClassDB::bind_method(D_METHOD("get_delimiter_start_position", "line", "column"), &CodeEdit::get_delimiter_start_position);
2541 ClassDB::bind_method(D_METHOD("get_delimiter_end_position", "line", "column"), &CodeEdit::get_delimiter_end_position);
2542
2543 /* Code hint */
2544 ClassDB::bind_method(D_METHOD("set_code_hint", "code_hint"), &CodeEdit::set_code_hint);
2545 ClassDB::bind_method(D_METHOD("set_code_hint_draw_below", "draw_below"), &CodeEdit::set_code_hint_draw_below);
2546
2547 /* Code Completion */
2548 BIND_ENUM_CONSTANT(KIND_CLASS);
2549 BIND_ENUM_CONSTANT(KIND_FUNCTION);
2550 BIND_ENUM_CONSTANT(KIND_SIGNAL);
2551 BIND_ENUM_CONSTANT(KIND_VARIABLE);
2552 BIND_ENUM_CONSTANT(KIND_MEMBER);
2553 BIND_ENUM_CONSTANT(KIND_ENUM);
2554 BIND_ENUM_CONSTANT(KIND_CONSTANT);
2555 BIND_ENUM_CONSTANT(KIND_NODE_PATH);
2556 BIND_ENUM_CONSTANT(KIND_FILE_PATH);
2557 BIND_ENUM_CONSTANT(KIND_PLAIN_TEXT);
2558
2559 BIND_ENUM_CONSTANT(LOCATION_LOCAL);
2560 BIND_ENUM_CONSTANT(LOCATION_PARENT_MASK);
2561 BIND_ENUM_CONSTANT(LOCATION_OTHER_USER_CODE)
2562 BIND_ENUM_CONSTANT(LOCATION_OTHER);
2563
2564 ClassDB::bind_method(D_METHOD("get_text_for_code_completion"), &CodeEdit::get_text_for_code_completion);
2565 ClassDB::bind_method(D_METHOD("request_code_completion", "force"), &CodeEdit::request_code_completion, DEFVAL(false));
2566 ClassDB::bind_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value", "location"), &CodeEdit::add_code_completion_option, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL), DEFVAL(LOCATION_OTHER));
2567 ClassDB::bind_method(D_METHOD("update_code_completion_options", "force"), &CodeEdit::update_code_completion_options);
2568 ClassDB::bind_method(D_METHOD("get_code_completion_options"), &CodeEdit::get_code_completion_options);
2569 ClassDB::bind_method(D_METHOD("get_code_completion_option", "index"), &CodeEdit::get_code_completion_option);
2570 ClassDB::bind_method(D_METHOD("get_code_completion_selected_index"), &CodeEdit::get_code_completion_selected_index);
2571 ClassDB::bind_method(D_METHOD("set_code_completion_selected_index", "index"), &CodeEdit::set_code_completion_selected_index);
2572
2573 ClassDB::bind_method(D_METHOD("confirm_code_completion", "replace"), &CodeEdit::confirm_code_completion, DEFVAL(false));
2574 ClassDB::bind_method(D_METHOD("cancel_code_completion"), &CodeEdit::cancel_code_completion);
2575
2576 ClassDB::bind_method(D_METHOD("set_code_completion_enabled", "enable"), &CodeEdit::set_code_completion_enabled);
2577 ClassDB::bind_method(D_METHOD("is_code_completion_enabled"), &CodeEdit::is_code_completion_enabled);
2578
2579 ClassDB::bind_method(D_METHOD("set_code_completion_prefixes", "prefixes"), &CodeEdit::set_code_completion_prefixes);
2580 ClassDB::bind_method(D_METHOD("get_code_completion_prefixes"), &CodeEdit::get_code_completion_prefixes);
2581
2582 // Overridable
2583
2584 GDVIRTUAL_BIND(_confirm_code_completion, "replace")
2585 GDVIRTUAL_BIND(_request_code_completion, "force")
2586 GDVIRTUAL_BIND(_filter_code_completion_candidates, "candidates")
2587
2588 /* Line length guidelines */
2589 ClassDB::bind_method(D_METHOD("set_line_length_guidelines", "guideline_columns"), &CodeEdit::set_line_length_guidelines);
2590 ClassDB::bind_method(D_METHOD("get_line_length_guidelines"), &CodeEdit::get_line_length_guidelines);
2591
2592 /* Symbol lookup */
2593 ClassDB::bind_method(D_METHOD("set_symbol_lookup_on_click_enabled", "enable"), &CodeEdit::set_symbol_lookup_on_click_enabled);
2594 ClassDB::bind_method(D_METHOD("is_symbol_lookup_on_click_enabled"), &CodeEdit::is_symbol_lookup_on_click_enabled);
2595
2596 ClassDB::bind_method(D_METHOD("get_text_for_symbol_lookup"), &CodeEdit::get_text_for_symbol_lookup);
2597 ClassDB::bind_method(D_METHOD("get_text_with_cursor_char", "line", "column"), &CodeEdit::get_text_with_cursor_char);
2598
2599 ClassDB::bind_method(D_METHOD("set_symbol_lookup_word_as_valid", "valid"), &CodeEdit::set_symbol_lookup_word_as_valid);
2600
2601 /* Inspector */
2602 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "symbol_lookup_on_click"), "set_symbol_lookup_on_click_enabled", "is_symbol_lookup_on_click_enabled");
2603 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "line_folding"), "set_line_folding_enabled", "is_line_folding_enabled");
2604
2605 ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "line_length_guidelines"), "set_line_length_guidelines", "get_line_length_guidelines");
2606
2607 ADD_GROUP("Gutters", "gutters_");
2608 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_breakpoints_gutter"), "set_draw_breakpoints_gutter", "is_drawing_breakpoints_gutter");
2609
2610 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_bookmarks"), "set_draw_bookmarks_gutter", "is_drawing_bookmarks_gutter");
2611
2612 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_executing_lines"), "set_draw_executing_lines_gutter", "is_drawing_executing_lines_gutter");
2613
2614 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_line_numbers"), "set_draw_line_numbers", "is_draw_line_numbers_enabled");
2615 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_zero_pad_line_numbers"), "set_line_numbers_zero_padded", "is_line_numbers_zero_padded");
2616
2617 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gutters_draw_fold_gutter"), "set_draw_fold_gutter", "is_drawing_fold_gutter");
2618
2619 ADD_GROUP("Delimiters", "delimiter_");
2620 ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_strings"), "set_string_delimiters", "get_string_delimiters");
2621 ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "delimiter_comments"), "set_comment_delimiters", "get_comment_delimiters");
2622
2623 ADD_GROUP("Code Completion", "code_completion_");
2624 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled");
2625 ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_completion_prefixes");
2626
2627 ADD_GROUP("Indentation", "indent_");
2628 ADD_PROPERTY(PropertyInfo(Variant::INT, "indent_size"), "set_indent_size", "get_indent_size");
2629 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_use_spaces"), "set_indent_using_spaces", "is_indent_using_spaces");
2630 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
2631 ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
2632
2633 ADD_GROUP("Auto Brace Completion", "auto_brace_completion_");
2634 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_enabled"), "set_auto_brace_completion_enabled", "is_auto_brace_completion_enabled");
2635 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_brace_completion_highlight_matching"), "set_highlight_matching_braces_enabled", "is_highlight_matching_braces_enabled");
2636 ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "auto_brace_completion_pairs"), "set_auto_brace_completion_pairs", "get_auto_brace_completion_pairs");
2637
2638 /* Signals */
2639 /* Gutters */
2640 ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line")));
2641
2642 /* Code Completion */
2643 ADD_SIGNAL(MethodInfo("code_completion_requested"));
2644
2645 /* Symbol lookup */
2646 ADD_SIGNAL(MethodInfo("symbol_lookup", PropertyInfo(Variant::STRING, "symbol"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::INT, "column")));
2647 ADD_SIGNAL(MethodInfo("symbol_validate", PropertyInfo(Variant::STRING, "symbol")));
2648
2649 /* Theme items */
2650 /* Gutters */
2651 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, code_folding_color);
2652 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, folded_code_region_color);
2653 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, can_fold_icon, "can_fold");
2654 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, folded_icon, "folded");
2655 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, can_fold_code_region_icon, "can_fold_code_region");
2656 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, folded_code_region_icon, "folded_code_region");
2657 BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CodeEdit, folded_eol_icon);
2658
2659 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, breakpoint_color);
2660 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, breakpoint_icon, "breakpoint");
2661
2662 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, bookmark_color);
2663 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, bookmark_icon, "bookmark");
2664
2665 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, executing_line_color);
2666 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, CodeEdit, executing_line_icon, "executing_line");
2667
2668 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, line_number_color);
2669
2670 /* Code Completion */
2671 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, CodeEdit, code_completion_style, "completion");
2672 BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_CONSTANT, CodeEdit, code_completion_icon_separation, "h_separation", "ItemList");
2673
2674 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, CodeEdit, code_completion_max_width, "completion_max_width");
2675 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, CodeEdit, code_completion_max_lines, "completion_lines");
2676 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, CodeEdit, code_completion_scroll_width, "completion_scroll_width");
2677 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, CodeEdit, code_completion_scroll_color, "completion_scroll_color");
2678 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, CodeEdit, code_completion_scroll_hovered_color, "completion_scroll_hovered_color");
2679 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, CodeEdit, code_completion_background_color, "completion_background_color");
2680 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, CodeEdit, code_completion_selected_color, "completion_selected_color");
2681 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, CodeEdit, code_completion_existing_color, "completion_existing_color");
2682
2683 /* Code hint */
2684 BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_STYLEBOX, CodeEdit, code_hint_style, "panel", "TooltipPanel");
2685 BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, CodeEdit, code_hint_color, "font_color", "TooltipLabel");
2686
2687 /* Line length guideline */
2688 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, line_length_guideline_color);
2689
2690 /* Other visuals */
2691 BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, CodeEdit, style_normal, "normal");
2692
2693 BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, CodeEdit, brace_mismatch_color);
2694
2695 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, CodeEdit, font);
2696 BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, CodeEdit, font_size);
2697
2698 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, CodeEdit, line_spacing);
2699}
2700
2701/* Auto brace completion */
2702int CodeEdit::_get_auto_brace_pair_open_at_pos(int p_line, int p_col) {
2703 const String &line = get_line(p_line);
2704
2705 /* Should be fast enough, expecting low amount of pairs... */
2706 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
2707 const String &open_key = auto_brace_completion_pairs[i].open_key;
2708 if (p_col - open_key.length() < 0) {
2709 continue;
2710 }
2711
2712 bool is_match = true;
2713 for (int j = 0; j < open_key.length(); j++) {
2714 if (line[(p_col - 1) - j] != open_key[(open_key.length() - 1) - j]) {
2715 is_match = false;
2716 break;
2717 }
2718 }
2719
2720 if (is_match) {
2721 return i;
2722 }
2723 }
2724 return -1;
2725}
2726
2727int CodeEdit::_get_auto_brace_pair_close_at_pos(int p_line, int p_col) {
2728 const String &line = get_line(p_line);
2729
2730 /* Should be fast enough, expecting low amount of pairs... */
2731 for (int i = 0; i < auto_brace_completion_pairs.size(); i++) {
2732 if (p_col + auto_brace_completion_pairs[i].close_key.length() > line.length()) {
2733 continue;
2734 }
2735
2736 bool is_match = true;
2737 for (int j = 0; j < auto_brace_completion_pairs[i].close_key.length(); j++) {
2738 if (line[p_col + j] != auto_brace_completion_pairs[i].close_key[j]) {
2739 is_match = false;
2740 break;
2741 }
2742 }
2743
2744 if (is_match) {
2745 return i;
2746 }
2747 }
2748 return -1;
2749}
2750
2751/* Gutters */
2752void CodeEdit::_gutter_clicked(int p_line, int p_gutter) {
2753 bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT);
2754
2755 if (p_gutter == main_gutter) {
2756 if (draw_breakpoints && !shift_pressed) {
2757 set_line_as_breakpoint(p_line, !is_line_breakpointed(p_line));
2758 } else if (draw_bookmarks && shift_pressed) {
2759 set_line_as_bookmarked(p_line, !is_line_bookmarked(p_line));
2760 }
2761 return;
2762 }
2763
2764 if (p_gutter == line_number_gutter) {
2765 remove_secondary_carets();
2766 set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_LINE, p_line, 0);
2767 select(p_line, 0, p_line + 1, 0);
2768 set_caret_line(p_line + 1);
2769 set_caret_column(0);
2770 return;
2771 }
2772
2773 if (p_gutter == fold_gutter) {
2774 if (is_line_folded(p_line)) {
2775 unfold_line(p_line);
2776 } else if (can_fold_line(p_line)) {
2777 fold_line(p_line);
2778 }
2779 return;
2780 }
2781}
2782
2783void CodeEdit::_update_gutter_indexes() {
2784 for (int i = 0; i < get_gutter_count(); i++) {
2785 if (get_gutter_name(i) == "main_gutter") {
2786 main_gutter = i;
2787 continue;
2788 }
2789
2790 if (get_gutter_name(i) == "line_numbers") {
2791 line_number_gutter = i;
2792 continue;
2793 }
2794
2795 if (get_gutter_name(i) == "fold_gutter") {
2796 fold_gutter = i;
2797 continue;
2798 }
2799 }
2800}
2801
2802/* Code Region */
2803void CodeEdit::_update_code_region_tags() {
2804 code_region_start_string = "";
2805 code_region_end_string = "";
2806
2807 if (code_region_start_tag.is_empty() || code_region_end_tag.is_empty()) {
2808 return;
2809 }
2810
2811 for (int i = 0; i < delimiters.size(); i++) {
2812 if (delimiters[i].type != DelimiterType::TYPE_COMMENT) {
2813 continue;
2814 }
2815 if (delimiters[i].end_key.is_empty() && delimiters[i].line_only == true) {
2816 code_region_start_string = delimiters[i].start_key + code_region_start_tag;
2817 code_region_end_string = delimiters[i].start_key + code_region_end_tag;
2818 return;
2819 }
2820 }
2821}
2822
2823/* Delimiters */
2824void CodeEdit::_update_delimiter_cache(int p_from_line, int p_to_line) {
2825 if (delimiters.size() == 0) {
2826 return;
2827 }
2828
2829 int line_count = get_line_count();
2830 if (p_to_line == -1) {
2831 p_to_line = line_count;
2832 }
2833
2834 int start_line = MIN(p_from_line, p_to_line);
2835 int end_line = MAX(p_from_line, p_to_line);
2836
2837 /* Make sure delimiter_cache has all the lines. */
2838 if (start_line != end_line) {
2839 if (p_to_line < p_from_line) {
2840 for (int i = end_line; i > start_line; i--) {
2841 delimiter_cache.remove_at(i);
2842 }
2843 } else {
2844 for (int i = start_line; i < end_line; i++) {
2845 delimiter_cache.insert(i, RBMap<int, int>());
2846 }
2847 }
2848 }
2849
2850 int in_region = -1;
2851 for (int i = start_line; i < MIN(end_line + 1, line_count); i++) {
2852 int current_end_region = (i <= 0 || delimiter_cache[i].size() < 1) ? -1 : delimiter_cache[i].back()->value();
2853 in_region = (i <= 0 || delimiter_cache[i - 1].size() < 1) ? -1 : delimiter_cache[i - 1].back()->value();
2854
2855 const String &str = get_line(i);
2856 const int line_length = str.length();
2857 delimiter_cache.write[i].clear();
2858
2859 if (str.length() == 0) {
2860 if (in_region != -1) {
2861 delimiter_cache.write[i][0] = in_region;
2862 }
2863 if (i == end_line && current_end_region != in_region) {
2864 end_line++;
2865 end_line = MIN(end_line, line_count);
2866 }
2867 continue;
2868 }
2869
2870 int end_region = -1;
2871 for (int j = 0; j < line_length; j++) {
2872 int from = j;
2873 for (; from < line_length; from++) {
2874 if (str[from] == '\\') {
2875 from++;
2876 continue;
2877 }
2878 break;
2879 }
2880
2881 /* check if we are in entering a region */
2882 bool same_line = false;
2883 if (in_region == -1) {
2884 for (int d = 0; d < delimiters.size(); d++) {
2885 /* check there is enough room */
2886 int chars_left = line_length - from;
2887 int start_key_length = delimiters[d].start_key.length();
2888 int end_key_length = delimiters[d].end_key.length();
2889 if (chars_left < start_key_length) {
2890 continue;
2891 }
2892
2893 /* search the line */
2894 bool match = true;
2895 const char32_t *start_key = delimiters[d].start_key.get_data();
2896 for (int k = 0; k < start_key_length; k++) {
2897 if (start_key[k] != str[from + k]) {
2898 match = false;
2899 break;
2900 }
2901 }
2902 if (!match) {
2903 continue;
2904 }
2905 same_line = true;
2906 in_region = d;
2907 delimiter_cache.write[i][from + 1] = d;
2908 from += start_key_length;
2909
2910 /* check if it's the whole line */
2911 if (end_key_length == 0 || delimiters[d].line_only || from + end_key_length > line_length) {
2912 j = line_length;
2913 if (delimiters[d].line_only) {
2914 delimiter_cache.write[i][line_length + 1] = -1;
2915 } else {
2916 end_region = in_region;
2917 }
2918 }
2919 break;
2920 }
2921
2922 if (j == line_length || in_region == -1) {
2923 continue;
2924 }
2925 }
2926
2927 /* if we are in one find the end key */
2928 /* search the line */
2929 int region_end_index = -1;
2930 int end_key_length = delimiters[in_region].end_key.length();
2931 const char32_t *end_key = delimiters[in_region].end_key.get_data();
2932 for (; from < line_length; from++) {
2933 if (line_length - from < end_key_length) {
2934 break;
2935 }
2936
2937 if (!is_symbol(str[from])) {
2938 continue;
2939 }
2940
2941 if (str[from] == '\\') {
2942 from++;
2943 continue;
2944 }
2945
2946 region_end_index = from;
2947 for (int k = 0; k < end_key_length; k++) {
2948 if (end_key[k] != str[from + k]) {
2949 region_end_index = -1;
2950 break;
2951 }
2952 }
2953
2954 if (region_end_index != -1) {
2955 break;
2956 }
2957 }
2958
2959 j = from + (end_key_length - 1);
2960 end_region = (region_end_index == -1) ? in_region : -1;
2961 if (!same_line || region_end_index != -1) {
2962 delimiter_cache.write[i][j + 1] = end_region;
2963 }
2964 in_region = -1;
2965 }
2966
2967 if (i == end_line && current_end_region != end_region) {
2968 end_line++;
2969 end_line = MIN(end_line, line_count);
2970 }
2971 }
2972}
2973
2974int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const {
2975 if (delimiters.size() == 0) {
2976 return -1;
2977 }
2978 ERR_FAIL_INDEX_V(p_line, get_line_count(), 0);
2979
2980 int region = (p_line <= 0 || delimiter_cache[p_line - 1].size() < 1) ? -1 : delimiter_cache[p_line - 1].back()->value();
2981 bool in_region = region != -1 && delimiters[region].type == p_type;
2982 for (RBMap<int, int>::Element *E = delimiter_cache[p_line].front(); E; E = E->next()) {
2983 /* If column is specified, loop until the key is larger then the column. */
2984 if (p_column != -1) {
2985 if (E->key() > p_column) {
2986 break;
2987 }
2988 in_region = E->value() != -1 && delimiters[E->value()].type == p_type;
2989 region = in_region ? E->value() : -1;
2990 continue;
2991 }
2992
2993 /* If no column, calculate if the entire line is a region */
2994 /* excluding whitespace. */
2995 const String line = get_line(p_line);
2996 if (!in_region) {
2997 if (E->value() == -1 || delimiters[E->value()].type != p_type) {
2998 break;
2999 }
3000
3001 region = E->value();
3002 in_region = true;
3003 for (int i = E->key() - 2; i >= 0; i--) {
3004 if (!is_whitespace(line[i])) {
3005 return -1;
3006 }
3007 }
3008 }
3009
3010 if (delimiters[region].line_only) {
3011 return region;
3012 }
3013
3014 int end_col = E->key();
3015 if (E->value() != -1) {
3016 if (!E->next()) {
3017 return region;
3018 }
3019 end_col = E->next()->key();
3020 }
3021
3022 for (int i = end_col; i < line.length(); i++) {
3023 if (!is_whitespace(line[i])) {
3024 return -1;
3025 }
3026 }
3027 return region;
3028 }
3029 return in_region ? region : -1;
3030}
3031
3032void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) {
3033 // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector.
3034 if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) {
3035 ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty");
3036
3037 for (int i = 0; i < p_start_key.length(); i++) {
3038 ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol");
3039 }
3040 }
3041
3042 if (p_end_key.length() > 0) {
3043 for (int i = 0; i < p_end_key.length(); i++) {
3044 ERR_FAIL_COND_MSG(!is_symbol(p_end_key[i]), "delimiter must end with a symbol");
3045 }
3046 }
3047
3048 int at = 0;
3049 for (int i = 0; i < delimiters.size(); i++) {
3050 ERR_FAIL_COND_MSG(delimiters[i].start_key == p_start_key, "delimiter with start key '" + p_start_key + "' already exists.");
3051 if (p_start_key.length() < delimiters[i].start_key.length()) {
3052 at++;
3053 }
3054 }
3055
3056 Delimiter delimiter;
3057 delimiter.type = p_type;
3058 delimiter.start_key = p_start_key;
3059 delimiter.end_key = p_end_key;
3060 delimiter.line_only = p_line_only || p_end_key.is_empty();
3061 delimiters.insert(at, delimiter);
3062 if (!setting_delimiters) {
3063 delimiter_cache.clear();
3064 _update_delimiter_cache();
3065 }
3066 if (p_type == DelimiterType::TYPE_COMMENT) {
3067 _update_code_region_tags();
3068 }
3069}
3070
3071void CodeEdit::_remove_delimiter(const String &p_start_key, DelimiterType p_type) {
3072 for (int i = 0; i < delimiters.size(); i++) {
3073 if (delimiters[i].start_key != p_start_key) {
3074 continue;
3075 }
3076
3077 if (delimiters[i].type != p_type) {
3078 break;
3079 }
3080
3081 delimiters.remove_at(i);
3082 if (!setting_delimiters) {
3083 delimiter_cache.clear();
3084 _update_delimiter_cache();
3085 }
3086 if (p_type == DelimiterType::TYPE_COMMENT) {
3087 _update_code_region_tags();
3088 }
3089 break;
3090 }
3091}
3092
3093bool CodeEdit::_has_delimiter(const String &p_start_key, DelimiterType p_type) const {
3094 for (int i = 0; i < delimiters.size(); i++) {
3095 if (delimiters[i].start_key == p_start_key) {
3096 return delimiters[i].type == p_type;
3097 }
3098 }
3099 return false;
3100}
3101
3102void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type) {
3103 setting_delimiters = true;
3104 _clear_delimiters(p_type);
3105
3106 for (int i = 0; i < p_delimiters.size(); i++) {
3107 String key = p_delimiters[i];
3108
3109 if (key.is_empty()) {
3110 continue;
3111 }
3112
3113 const String start_key = key.get_slice(" ", 0);
3114 const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String();
3115
3116 _add_delimiter(start_key, end_key, end_key.is_empty(), p_type);
3117 }
3118 setting_delimiters = false;
3119 _update_delimiter_cache();
3120}
3121
3122void CodeEdit::_clear_delimiters(DelimiterType p_type) {
3123 for (int i = delimiters.size() - 1; i >= 0; i--) {
3124 if (delimiters[i].type == p_type) {
3125 delimiters.remove_at(i);
3126 }
3127 }
3128 delimiter_cache.clear();
3129 if (!setting_delimiters) {
3130 _update_delimiter_cache();
3131 }
3132 if (p_type == DelimiterType::TYPE_COMMENT) {
3133 _update_code_region_tags();
3134 }
3135}
3136
3137TypedArray<String> CodeEdit::_get_delimiters(DelimiterType p_type) const {
3138 TypedArray<String> r_delimiters;
3139 for (int i = 0; i < delimiters.size(); i++) {
3140 if (delimiters[i].type != p_type) {
3141 continue;
3142 }
3143 r_delimiters.push_back(delimiters[i].start_key + (delimiters[i].end_key.is_empty() ? "" : " " + delimiters[i].end_key));
3144 }
3145 return r_delimiters;
3146}
3147
3148/* Code Completion */
3149void CodeEdit::_update_scroll_selected_line(float p_mouse_y) {
3150 float percent = (float)(p_mouse_y - code_completion_scroll_rect.position.y) / code_completion_scroll_rect.size.height;
3151 percent = CLAMP(percent, 0.0f, 1.0f);
3152
3153 code_completion_current_selected = (int)(percent * (code_completion_options.size() - 1));
3154 code_completion_force_item_center = -1;
3155}
3156
3157void CodeEdit::_filter_code_completion_candidates_impl() {
3158 int line_height = get_line_height();
3159
3160 if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) {
3161 code_completion_options.clear();
3162 code_completion_base = "";
3163
3164 /* Build options argument. */
3165 TypedArray<Dictionary> completion_options_sources;
3166 completion_options_sources.resize(code_completion_option_sources.size());
3167 int i = 0;
3168 for (const ScriptLanguage::CodeCompletionOption &E : code_completion_option_sources) {
3169 Dictionary option;
3170 option["kind"] = E.kind;
3171 option["display_text"] = E.display;
3172 option["insert_text"] = E.insert_text;
3173 option["font_color"] = E.font_color;
3174 option["icon"] = E.icon;
3175 option["default_value"] = E.default_value;
3176 option["location"] = E.location;
3177 completion_options_sources[i] = option;
3178 i++;
3179 }
3180
3181 TypedArray<Dictionary> completion_options;
3182
3183 GDVIRTUAL_CALL(_filter_code_completion_candidates, completion_options_sources, completion_options);
3184
3185 /* No options to complete, cancel. */
3186 if (completion_options.size() == 0) {
3187 cancel_code_completion();
3188 return;
3189 }
3190
3191 /* Convert back into options. */
3192 int max_width = 0;
3193 for (i = 0; i < completion_options.size(); i++) {
3194 ScriptLanguage::CodeCompletionOption option;
3195 option.kind = (ScriptLanguage::CodeCompletionKind)(int)completion_options[i].get("kind");
3196 option.display = completion_options[i].get("display_text");
3197 option.insert_text = completion_options[i].get("insert_text");
3198 option.font_color = completion_options[i].get("font_color");
3199 option.icon = completion_options[i].get("icon");
3200 option.location = completion_options[i].get("location");
3201 option.default_value = completion_options[i].get("default_value");
3202
3203 int offset = 0;
3204 if (option.default_value.get_type() == Variant::COLOR) {
3205 offset = line_height;
3206 }
3207
3208 if (theme_cache.font.is_valid()) {
3209 max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
3210 }
3211 code_completion_options.push_back(option);
3212 }
3213
3214 code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
3215 code_completion_current_selected = 0;
3216 code_completion_force_item_center = -1;
3217 code_completion_active = true;
3218 queue_redraw();
3219 return;
3220 }
3221
3222 const int caret_line = get_caret_line();
3223 const int caret_column = get_caret_column();
3224 const String line = get_line(caret_line);
3225 ERR_FAIL_INDEX_MSG(caret_column, line.length() + 1, "Caret column exceeds line length.");
3226
3227 if (caret_column > 0 && line[caret_column - 1] == '(' && !code_completion_forced) {
3228 cancel_code_completion();
3229 return;
3230 }
3231
3232 /* Get string status, are we in one or at the close. */
3233 int in_string = is_in_string(caret_line, caret_column);
3234 int first_quote_col = -1;
3235 if (in_string != -1) {
3236 Point2 string_start_pos = get_delimiter_start_position(caret_line, caret_column);
3237 first_quote_col = (string_start_pos.y == caret_line) ? string_start_pos.x : -1;
3238 } else if (caret_column > 0) {
3239 if (is_in_string(caret_line, caret_column - 1) != -1) {
3240 first_quote_col = caret_column - 1;
3241 }
3242 }
3243
3244 int cofs = caret_column;
3245 String string_to_complete;
3246 bool prev_is_word = false;
3247
3248 /* Cancel if we are at the close of a string. */
3249 if (caret_column > 0 && in_string == -1 && first_quote_col == cofs - 1) {
3250 cancel_code_completion();
3251 return;
3252 /* In a string, therefore we are trying to complete the string text. */
3253 } else if (in_string != -1 && first_quote_col != -1) {
3254 int key_length = delimiters[in_string].start_key.length();
3255 string_to_complete = line.substr(first_quote_col - key_length, (cofs - first_quote_col) + key_length);
3256 /* If we have a space, previous word might be a keyword. eg "func |". */
3257 } else if (cofs > 0 && line[cofs - 1] == ' ') {
3258 int ofs = cofs - 1;
3259 while (ofs > 0 && line[ofs] == ' ') {
3260 ofs--;
3261 }
3262 prev_is_word = !is_symbol(line[ofs]);
3263 /* Otherwise get current word and set cofs to the start. */
3264 } else {
3265 int start_cofs = cofs;
3266 while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || !is_symbol(line[cofs - 1]))) {
3267 cofs--;
3268 }
3269 string_to_complete = line.substr(cofs, start_cofs - cofs);
3270 }
3271
3272 /* If all else fails, check for a prefix. */
3273 /* Single space between caret and prefix is okay. */
3274 bool prev_is_prefix = false;
3275 if (cofs > 0 && code_completion_prefixes.has(line[cofs - 1])) {
3276 prev_is_prefix = true;
3277 } else if (cofs > 1 && line[cofs - 1] == ' ' && code_completion_prefixes.has(line[cofs - 2])) {
3278 prev_is_prefix = true;
3279 }
3280
3281 if (!prev_is_word && string_to_complete.is_empty() && (cofs == 0 || !prev_is_prefix)) {
3282 cancel_code_completion();
3283 return;
3284 }
3285
3286 /* Filter Options. */
3287 /* For now handle only traditional quoted strings. */
3288 bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
3289
3290 code_completion_options.clear();
3291 code_completion_base = string_to_complete;
3292
3293 /* Don't autocomplete setting numerical values. */
3294 if (code_completion_base.is_numeric()) {
3295 cancel_code_completion();
3296 return;
3297 }
3298
3299 int max_width = 0;
3300 String string_to_complete_lower = string_to_complete.to_lower();
3301
3302 for (ScriptLanguage::CodeCompletionOption &option : code_completion_option_sources) {
3303 option.matches.clear();
3304 if (single_quote && option.display.is_quoted()) {
3305 option.display = option.display.unquote().quote("'");
3306 }
3307
3308 int offset = option.default_value.get_type() == Variant::COLOR ? line_height : 0;
3309
3310 if (in_string != -1) {
3311 String quote = single_quote ? "'" : "\"";
3312 option.display = option.display.unquote().quote(quote);
3313 option.insert_text = option.insert_text.unquote().quote(quote);
3314 }
3315
3316 if (option.display.length() == 0) {
3317 continue;
3318 }
3319
3320 if (string_to_complete.length() == 0) {
3321 option.get_option_characteristics(string_to_complete);
3322 code_completion_options.push_back(option);
3323 if (theme_cache.font.is_valid()) {
3324 max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
3325 }
3326 continue;
3327 }
3328
3329 String target_lower = option.display.to_lower();
3330 int long_option = target_lower.size() > 50;
3331 const char32_t *string_to_complete_char_lower = &string_to_complete_lower[0];
3332 const char32_t *target_char_lower = &target_lower[0];
3333
3334 Vector<Vector<Pair<int, int>>> all_possible_subsequence_matches;
3335 for (int i = 0; *target_char_lower; i++, target_char_lower++) {
3336 if (*target_char_lower == *string_to_complete_char_lower) {
3337 all_possible_subsequence_matches.push_back({ { i, 1 } });
3338 }
3339 }
3340 string_to_complete_char_lower++;
3341
3342 for (int i = 1; *string_to_complete_char_lower && (all_possible_subsequence_matches.size() > 0); i++, string_to_complete_char_lower++) {
3343 // find all occurrences of ssq_lower to avoid looking everywhere each time
3344 Vector<int> all_ocurence;
3345 if (long_option) {
3346 all_ocurence.push_back(target_lower.find_char(*string_to_complete_char_lower));
3347 } else {
3348 for (int j = i; j < target_lower.length(); j++) {
3349 if (target_lower[j] == *string_to_complete_char_lower) {
3350 all_ocurence.push_back(j);
3351 }
3352 }
3353 }
3354 Vector<Vector<Pair<int, int>>> next_subsequence_matches;
3355 for (Vector<Pair<int, int>> &subsequence_match : all_possible_subsequence_matches) {
3356 Pair<int, int> match_last_segment = subsequence_match[subsequence_match.size() - 1];
3357 int next_index = match_last_segment.first + match_last_segment.second;
3358 // get the last index from current sequence
3359 // and look for next char starting from that index
3360 if (target_lower[next_index] == *string_to_complete_char_lower) {
3361 Vector<Pair<int, int>> new_match = subsequence_match;
3362 new_match.write[new_match.size() - 1].second++;
3363 next_subsequence_matches.push_back(new_match);
3364 if (long_option) {
3365 continue;
3366 }
3367 }
3368 for (int index : all_ocurence) {
3369 if (index > next_index) {
3370 Vector<Pair<int, int>> new_match = subsequence_match;
3371 new_match.push_back({ index, 1 });
3372 next_subsequence_matches.push_back(new_match);
3373 }
3374 }
3375 }
3376 all_possible_subsequence_matches = next_subsequence_matches;
3377 }
3378 // go through all possible matches to get the best one as defined by CodeCompletionOptionCompare
3379 if (all_possible_subsequence_matches.size() > 0) {
3380 option.matches = all_possible_subsequence_matches[0];
3381 option.get_option_characteristics(string_to_complete);
3382 all_possible_subsequence_matches = all_possible_subsequence_matches.slice(1);
3383 if (all_possible_subsequence_matches.size() > 0) {
3384 CodeCompletionOptionCompare compare;
3385 ScriptLanguage::CodeCompletionOption compared_option = option;
3386 compared_option.clear_characteristics();
3387 for (Vector<Pair<int, int>> &matches : all_possible_subsequence_matches) {
3388 compared_option.matches = matches;
3389 compared_option.get_option_characteristics(string_to_complete);
3390 if (compare(compared_option, option)) {
3391 option = compared_option;
3392 compared_option.clear_characteristics();
3393 }
3394 }
3395 }
3396
3397 code_completion_options.push_back(option);
3398 if (theme_cache.font.is_valid()) {
3399 max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
3400 }
3401 }
3402 }
3403
3404 /* No options to complete, cancel. */
3405 if (code_completion_options.size() == 0) {
3406 cancel_code_completion();
3407 return;
3408 }
3409
3410 /* A perfect match, stop completion. */
3411 if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) {
3412 cancel_code_completion();
3413 return;
3414 }
3415
3416 code_completion_options.sort_custom<CodeCompletionOptionCompare>();
3417
3418 code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
3419 code_completion_current_selected = 0;
3420 code_completion_force_item_center = -1;
3421 code_completion_active = true;
3422 queue_redraw();
3423}
3424
3425void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
3426 _update_delimiter_cache(p_from_line, p_to_line);
3427
3428 if (p_from_line == p_to_line) {
3429 return;
3430 }
3431
3432 lines_edited_changed += p_to_line - p_from_line;
3433 lines_edited_from = (lines_edited_from == -1) ? MIN(p_from_line, p_to_line) : MIN(lines_edited_from, MIN(p_from_line, p_to_line));
3434 lines_edited_to = (lines_edited_to == -1) ? MAX(p_from_line, p_to_line) : MAX(lines_edited_from, MAX(p_from_line, p_to_line));
3435}
3436
3437void CodeEdit::_text_set() {
3438 lines_edited_from = 0;
3439 lines_edited_to = 9999;
3440 _text_changed();
3441}
3442
3443void CodeEdit::_text_changed() {
3444 if (lines_edited_from == -1) {
3445 return;
3446 }
3447
3448 int lc = get_line_count();
3449 line_number_digits = 1;
3450 while (lc /= 10) {
3451 line_number_digits++;
3452 }
3453
3454 if (theme_cache.font.is_valid()) {
3455 set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width);
3456 }
3457
3458 lc = get_line_count();
3459 List<int> breakpoints;
3460 for (const KeyValue<int, bool> &E : breakpointed_lines) {
3461 breakpoints.push_back(E.key);
3462 }
3463 for (const int &line : breakpoints) {
3464 if (line < lines_edited_from || (line < lc && is_line_breakpointed(line))) {
3465 continue;
3466 }
3467
3468 breakpointed_lines.erase(line);
3469 emit_signal(SNAME("breakpoint_toggled"), line);
3470
3471 int next_line = line + lines_edited_changed;
3472 if (next_line > -1 && next_line < lc && is_line_breakpointed(next_line)) {
3473 emit_signal(SNAME("breakpoint_toggled"), next_line);
3474 breakpointed_lines[next_line] = true;
3475 continue;
3476 }
3477 }
3478
3479 lines_edited_from = -1;
3480 lines_edited_to = -1;
3481 lines_edited_changed = 0;
3482}
3483
3484CodeEdit::CodeEdit() {
3485 /* Indent management */
3486 auto_indent_prefixes.insert(':');
3487 auto_indent_prefixes.insert('{');
3488 auto_indent_prefixes.insert('[');
3489 auto_indent_prefixes.insert('(');
3490
3491 /* Auto brace completion */
3492 add_auto_brace_completion_pair("(", ")");
3493 add_auto_brace_completion_pair("{", "}");
3494 add_auto_brace_completion_pair("[", "]");
3495 add_auto_brace_completion_pair("\"", "\"");
3496 add_auto_brace_completion_pair("\'", "\'");
3497
3498 /* Delimiter tracking */
3499 add_string_delimiter("\"", "\"", false);
3500 add_string_delimiter("\'", "\'", false);
3501
3502 /* Text Direction */
3503 set_layout_direction(LAYOUT_DIRECTION_LTR);
3504 set_text_direction(TEXT_DIRECTION_LTR);
3505
3506 /* Gutters */
3507 int gutter_idx = 0;
3508
3509 /* Main Gutter */
3510 add_gutter();
3511 set_gutter_name(gutter_idx, "main_gutter");
3512 set_gutter_draw(gutter_idx, false);
3513 set_gutter_overwritable(gutter_idx, true);
3514 set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM);
3515 set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_main_gutter_draw_callback));
3516 gutter_idx++;
3517
3518 /* Line numbers */
3519 add_gutter();
3520 set_gutter_name(gutter_idx, "line_numbers");
3521 set_gutter_draw(gutter_idx, false);
3522 set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM);
3523 set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_line_number_draw_callback));
3524 gutter_idx++;
3525
3526 /* Fold Gutter */
3527 add_gutter();
3528 set_gutter_name(gutter_idx, "fold_gutter");
3529 set_gutter_draw(gutter_idx, false);
3530 set_gutter_type(gutter_idx, GUTTER_TYPE_CUSTOM);
3531 set_gutter_custom_draw(gutter_idx, callable_mp(this, &CodeEdit::_fold_gutter_draw_callback));
3532 gutter_idx++;
3533
3534 connect("lines_edited_from", callable_mp(this, &CodeEdit::_lines_edited_from));
3535 connect("text_set", callable_mp(this, &CodeEdit::_text_set));
3536 connect("text_changed", callable_mp(this, &CodeEdit::_text_changed));
3537
3538 connect("gutter_clicked", callable_mp(this, &CodeEdit::_gutter_clicked));
3539 connect("gutter_added", callable_mp(this, &CodeEdit::_update_gutter_indexes));
3540 connect("gutter_removed", callable_mp(this, &CodeEdit::_update_gutter_indexes));
3541 _update_gutter_indexes();
3542}
3543
3544CodeEdit::~CodeEdit() {
3545}
3546
3547// Return true if l should come before r
3548bool CodeCompletionOptionCompare::operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const {
3549 TypedArray<int> lcharac = l.get_option_cached_characteristics();
3550 TypedArray<int> rcharac = r.get_option_cached_characteristics();
3551
3552 if (lcharac != rcharac) {
3553 return lcharac < rcharac;
3554 }
3555
3556 // to get here they need to have the same size so we can take the size of whichever we want
3557 for (int i = 0; i < l.matches.size(); ++i) {
3558 if (l.matches[i].first != r.matches[i].first) {
3559 return l.matches[i].first < r.matches[i].first;
3560 }
3561 if (l.matches[i].second != r.matches[i].second) {
3562 return l.matches[i].second > r.matches[i].second;
3563 }
3564 }
3565 return l.display.naturalnocasecmp_to(r.display) < 0;
3566}
3567