Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/script/text_editor.cpp
9903 views
1
/**************************************************************************/
2
/* text_editor.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "text_editor.h"
32
33
#include "core/io/json.h"
34
#include "editor/editor_node.h"
35
#include "editor/settings/editor_settings.h"
36
#include "scene/gui/menu_button.h"
37
#include "scene/gui/split_container.h"
38
39
void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
40
ERR_FAIL_COND(p_highlighter.is_null());
41
42
highlighters[p_highlighter->_get_name()] = p_highlighter;
43
highlighter_menu->add_radio_check_item(p_highlighter->_get_name());
44
}
45
46
void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
47
ERR_FAIL_COND(p_highlighter.is_null());
48
49
HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin();
50
while (el) {
51
int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key);
52
highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter);
53
++el;
54
}
55
56
CodeEdit *te = code_editor->get_text_editor();
57
te->set_syntax_highlighter(p_highlighter);
58
}
59
60
void TextEditor::_change_syntax_highlighter(int p_idx) {
61
set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);
62
}
63
64
void TextEditor::_load_theme_settings() {
65
code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
66
}
67
68
String TextEditor::get_name() {
69
String name;
70
71
name = edited_res->get_path().get_file();
72
if (name.is_empty()) {
73
// This appears for newly created built-in text_files before saving the scene.
74
name = TTR("[unsaved]");
75
} else if (edited_res->is_built_in()) {
76
const String &text_file_name = edited_res->get_name();
77
if (!text_file_name.is_empty()) {
78
// If the built-in text_file has a custom resource name defined,
79
// display the built-in text_file name as follows: `ResourceName (scene_file.tscn)`
80
name = vformat("%s (%s)", text_file_name, name.get_slice("::", 0));
81
}
82
}
83
84
if (is_unsaved()) {
85
name += "(*)";
86
}
87
88
return name;
89
}
90
91
Ref<Texture2D> TextEditor::get_theme_icon() {
92
return EditorNode::get_singleton()->get_object_icon(edited_res.ptr(), "TextFile");
93
}
94
95
Ref<Resource> TextEditor::get_edited_resource() const {
96
return edited_res;
97
}
98
99
void TextEditor::set_edited_resource(const Ref<Resource> &p_res) {
100
ERR_FAIL_COND(edited_res.is_valid());
101
ERR_FAIL_COND(p_res.is_null());
102
103
edited_res = p_res;
104
105
Ref<TextFile> text_file = edited_res;
106
if (text_file.is_valid()) {
107
code_editor->get_text_editor()->set_text(text_file->get_text());
108
}
109
110
Ref<JSON> json_file = edited_res;
111
if (json_file.is_valid()) {
112
code_editor->get_text_editor()->set_text(json_file->get_parsed_text());
113
}
114
115
code_editor->get_text_editor()->clear_undo_history();
116
code_editor->get_text_editor()->tag_saved_version();
117
118
emit_signal(SNAME("name_changed"));
119
code_editor->update_line_and_column();
120
}
121
122
void TextEditor::enable_editor(Control *p_shortcut_context) {
123
if (editor_enabled) {
124
return;
125
}
126
127
editor_enabled = true;
128
129
_load_theme_settings();
130
131
_validate_script();
132
133
if (p_shortcut_context) {
134
for (int i = 0; i < edit_hb->get_child_count(); ++i) {
135
Control *c = cast_to<Control>(edit_hb->get_child(i));
136
if (c) {
137
c->set_shortcut_context(p_shortcut_context);
138
}
139
}
140
}
141
}
142
143
void TextEditor::add_callback(const String &p_function, const PackedStringArray &p_args) {
144
}
145
146
void TextEditor::set_debugger_active(bool p_active) {
147
}
148
149
Control *TextEditor::get_base_editor() const {
150
return code_editor->get_text_editor();
151
}
152
153
CodeTextEditor *TextEditor::get_code_editor() const {
154
return code_editor;
155
}
156
157
PackedInt32Array TextEditor::get_breakpoints() {
158
return PackedInt32Array();
159
}
160
161
void TextEditor::reload_text() {
162
ERR_FAIL_COND(edited_res.is_null());
163
164
CodeEdit *te = code_editor->get_text_editor();
165
int column = te->get_caret_column();
166
int row = te->get_caret_line();
167
int h = te->get_h_scroll();
168
int v = te->get_v_scroll();
169
170
Ref<TextFile> text_file = edited_res;
171
if (text_file.is_valid()) {
172
te->set_text(text_file->get_text());
173
}
174
175
Ref<JSON> json_file = edited_res;
176
if (json_file.is_valid()) {
177
te->set_text(json_file->get_parsed_text());
178
}
179
180
te->set_caret_line(row);
181
te->set_caret_column(column);
182
te->set_h_scroll(h);
183
te->set_v_scroll(v);
184
185
te->tag_saved_version();
186
187
code_editor->update_line_and_column();
188
_validate_script();
189
}
190
191
void TextEditor::_validate_script() {
192
emit_signal(SNAME("name_changed"));
193
emit_signal(SNAME("edited_script_changed"));
194
195
Ref<JSON> json_file = edited_res;
196
if (json_file.is_valid()) {
197
CodeEdit *te = code_editor->get_text_editor();
198
199
te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0));
200
code_editor->set_error("");
201
202
if (json_file->parse(te->get_text(), true) != OK) {
203
code_editor->set_error(json_file->get_error_message().replace("[", "[lb]"));
204
code_editor->set_error_pos(json_file->get_error_line(), 0);
205
te->set_line_background_color(code_editor->get_error_pos().x, EDITOR_GET("text_editor/theme/highlighting/mark_color"));
206
}
207
}
208
}
209
210
void TextEditor::_update_bookmark_list() {
211
bookmarks_menu->clear();
212
213
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
214
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL);
215
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark"), BOOKMARK_GOTO_NEXT);
216
bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark"), BOOKMARK_GOTO_PREV);
217
218
PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines();
219
if (bookmark_list.is_empty()) {
220
return;
221
}
222
223
bookmarks_menu->add_separator();
224
225
for (int i = 0; i < bookmark_list.size(); i++) {
226
String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).strip_edges();
227
// Limit the size of the line if too big.
228
if (line.length() > 50) {
229
line = line.substr(0, 50);
230
}
231
232
bookmarks_menu->add_item(String::num_int64(bookmark_list[i] + 1) + " - \"" + line + "\"");
233
bookmarks_menu->set_item_metadata(-1, bookmark_list[i]);
234
}
235
}
236
237
void TextEditor::_bookmark_item_pressed(int p_idx) {
238
if (p_idx < 4) { // Any item before the separator.
239
_edit_option(bookmarks_menu->get_item_id(p_idx));
240
} else {
241
code_editor->goto_line(bookmarks_menu->get_item_metadata(p_idx));
242
}
243
}
244
245
void TextEditor::apply_code() {
246
Ref<TextFile> text_file = edited_res;
247
if (text_file.is_valid()) {
248
text_file->set_text(code_editor->get_text_editor()->get_text());
249
}
250
251
Ref<JSON> json_file = edited_res;
252
if (json_file.is_valid()) {
253
json_file->parse(code_editor->get_text_editor()->get_text(), true);
254
}
255
code_editor->get_text_editor()->get_syntax_highlighter()->update_cache();
256
}
257
258
bool TextEditor::is_unsaved() {
259
const bool unsaved =
260
code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() ||
261
edited_res->get_path().is_empty(); // In memory.
262
return unsaved;
263
}
264
265
Variant TextEditor::get_edit_state() {
266
return code_editor->get_edit_state();
267
}
268
269
void TextEditor::set_edit_state(const Variant &p_state) {
270
code_editor->set_edit_state(p_state);
271
272
Dictionary state = p_state;
273
if (state.has("syntax_highlighter")) {
274
int idx = highlighter_menu->get_item_idx_from_text(state["syntax_highlighter"]);
275
if (idx >= 0) {
276
_change_syntax_highlighter(idx);
277
}
278
}
279
280
ensure_focus();
281
}
282
283
Variant TextEditor::get_navigation_state() {
284
return code_editor->get_navigation_state();
285
}
286
287
void TextEditor::trim_trailing_whitespace() {
288
code_editor->trim_trailing_whitespace();
289
}
290
291
void TextEditor::trim_final_newlines() {
292
code_editor->trim_final_newlines();
293
}
294
295
void TextEditor::insert_final_newline() {
296
code_editor->insert_final_newline();
297
}
298
299
void TextEditor::convert_indent() {
300
code_editor->get_text_editor()->convert_indent();
301
}
302
303
void TextEditor::tag_saved_version() {
304
code_editor->get_text_editor()->tag_saved_version();
305
edited_file_data.last_modified_time = FileAccess::get_modified_time(edited_file_data.path);
306
}
307
308
void TextEditor::goto_line(int p_line, int p_column) {
309
code_editor->goto_line(p_line, p_column);
310
}
311
312
void TextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {
313
code_editor->goto_line_selection(p_line, p_begin, p_end);
314
}
315
316
void TextEditor::set_executing_line(int p_line) {
317
code_editor->set_executing_line(p_line);
318
}
319
320
void TextEditor::clear_executing_line() {
321
code_editor->clear_executing_line();
322
}
323
324
void TextEditor::ensure_focus() {
325
code_editor->get_text_editor()->grab_focus();
326
}
327
328
Vector<String> TextEditor::get_functions() {
329
return Vector<String>();
330
}
331
332
bool TextEditor::show_members_overview() {
333
return true;
334
}
335
336
void TextEditor::update_settings() {
337
code_editor->update_editor_settings();
338
}
339
340
void TextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) {
341
Variant args[1] = { this };
342
const Variant *argp[] = { &args[0] };
343
code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1));
344
}
345
346
Control *TextEditor::get_edit_menu() {
347
return edit_hb;
348
}
349
350
void TextEditor::clear_edit_menu() {
351
memdelete(edit_hb);
352
}
353
354
void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) {
355
code_editor->set_find_replace_bar(p_bar);
356
}
357
358
void TextEditor::_edit_option(int p_op) {
359
CodeEdit *tx = code_editor->get_text_editor();
360
tx->apply_ime();
361
362
switch (p_op) {
363
case EDIT_UNDO: {
364
tx->undo();
365
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
366
} break;
367
case EDIT_REDO: {
368
tx->redo();
369
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
370
} break;
371
case EDIT_CUT: {
372
tx->cut();
373
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
374
} break;
375
case EDIT_COPY: {
376
tx->copy();
377
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
378
} break;
379
case EDIT_PASTE: {
380
tx->paste();
381
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
382
} break;
383
case EDIT_SELECT_ALL: {
384
tx->select_all();
385
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
386
} break;
387
case EDIT_MOVE_LINE_UP: {
388
code_editor->get_text_editor()->move_lines_up();
389
} break;
390
case EDIT_MOVE_LINE_DOWN: {
391
code_editor->get_text_editor()->move_lines_down();
392
} break;
393
case EDIT_INDENT: {
394
tx->indent_lines();
395
} break;
396
case EDIT_UNINDENT: {
397
tx->unindent_lines();
398
} break;
399
case EDIT_DELETE_LINE: {
400
code_editor->get_text_editor()->delete_lines();
401
} break;
402
case EDIT_DUPLICATE_SELECTION: {
403
code_editor->get_text_editor()->duplicate_selection();
404
} break;
405
case EDIT_DUPLICATE_LINES: {
406
code_editor->get_text_editor()->duplicate_lines();
407
} break;
408
case EDIT_TOGGLE_FOLD_LINE: {
409
tx->toggle_foldable_lines_at_carets();
410
} break;
411
case EDIT_FOLD_ALL_LINES: {
412
tx->fold_all_lines();
413
} break;
414
case EDIT_UNFOLD_ALL_LINES: {
415
tx->unfold_all_lines();
416
} break;
417
case EDIT_TRIM_TRAILING_WHITESAPCE: {
418
trim_trailing_whitespace();
419
} break;
420
case EDIT_TRIM_FINAL_NEWLINES: {
421
trim_final_newlines();
422
} break;
423
case EDIT_CONVERT_INDENT_TO_SPACES: {
424
code_editor->set_indent_using_spaces(true);
425
convert_indent();
426
} break;
427
case EDIT_CONVERT_INDENT_TO_TABS: {
428
code_editor->set_indent_using_spaces(false);
429
convert_indent();
430
} break;
431
case EDIT_TO_UPPERCASE: {
432
_convert_case(CodeTextEditor::UPPER);
433
} break;
434
case EDIT_TO_LOWERCASE: {
435
_convert_case(CodeTextEditor::LOWER);
436
} break;
437
case EDIT_CAPITALIZE: {
438
_convert_case(CodeTextEditor::CAPITALIZE);
439
} break;
440
case EDIT_TOGGLE_WORD_WRAP: {
441
TextEdit::LineWrappingMode wrap = code_editor->get_text_editor()->get_line_wrapping_mode();
442
code_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY);
443
} break;
444
case SEARCH_FIND: {
445
code_editor->get_find_replace_bar()->popup_search();
446
} break;
447
case SEARCH_FIND_NEXT: {
448
code_editor->get_find_replace_bar()->search_next();
449
} break;
450
case SEARCH_FIND_PREV: {
451
code_editor->get_find_replace_bar()->search_prev();
452
} break;
453
case SEARCH_REPLACE: {
454
code_editor->get_find_replace_bar()->popup_replace();
455
} break;
456
case SEARCH_IN_FILES: {
457
String selected_text = code_editor->get_text_editor()->get_selected_text();
458
459
// Yep, because it doesn't make sense to instance this dialog for every single script open...
460
// So this will be delegated to the ScriptEditor.
461
emit_signal(SNAME("search_in_files_requested"), selected_text);
462
} break;
463
case REPLACE_IN_FILES: {
464
String selected_text = code_editor->get_text_editor()->get_selected_text();
465
466
emit_signal(SNAME("replace_in_files_requested"), selected_text);
467
} break;
468
case SEARCH_GOTO_LINE: {
469
goto_line_popup->popup_find_line(code_editor);
470
} break;
471
case BOOKMARK_TOGGLE: {
472
code_editor->toggle_bookmark();
473
} break;
474
case BOOKMARK_GOTO_NEXT: {
475
code_editor->goto_next_bookmark();
476
} break;
477
case BOOKMARK_GOTO_PREV: {
478
code_editor->goto_prev_bookmark();
479
} break;
480
case BOOKMARK_REMOVE_ALL: {
481
code_editor->remove_all_bookmarks();
482
} break;
483
case EDIT_EMOJI_AND_SYMBOL: {
484
code_editor->get_text_editor()->show_emoji_and_symbol_picker();
485
} break;
486
}
487
}
488
489
void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
490
code_editor->convert_case(p_case);
491
}
492
493
ScriptEditorBase *TextEditor::create_editor(const Ref<Resource> &p_resource) {
494
if (Object::cast_to<TextFile>(*p_resource) || Object::cast_to<JSON>(*p_resource)) {
495
return memnew(TextEditor);
496
}
497
return nullptr;
498
}
499
500
void TextEditor::register_editor() {
501
ScriptEditor::register_create_script_editor_function(create_editor);
502
}
503
504
void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {
505
Ref<InputEventMouseButton> mb = ev;
506
507
if (mb.is_valid()) {
508
if (mb->get_button_index() == MouseButton::RIGHT) {
509
CodeEdit *tx = code_editor->get_text_editor();
510
511
tx->apply_ime();
512
513
Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position());
514
int row = pos.y;
515
int col = pos.x;
516
517
tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click"));
518
bool can_fold = tx->can_fold_line(row);
519
bool is_folded = tx->is_line_folded(row);
520
521
if (tx->is_move_caret_on_right_click_enabled()) {
522
tx->remove_secondary_carets();
523
if (tx->has_selection()) {
524
int from_line = tx->get_selection_from_line();
525
int to_line = tx->get_selection_to_line();
526
int from_column = tx->get_selection_from_column();
527
int to_column = tx->get_selection_to_column();
528
529
if (row < from_line || row > to_line || (row == from_line && col < from_column) || (row == to_line && col > to_column)) {
530
// Right click is outside the selected text.
531
tx->deselect();
532
}
533
}
534
if (!tx->has_selection()) {
535
tx->set_caret_line(row, true, false, -1);
536
tx->set_caret_column(col);
537
}
538
}
539
540
if (!mb->is_pressed()) {
541
_make_context_menu(tx->has_selection(), can_fold, is_folded, get_local_mouse_position());
542
}
543
}
544
}
545
546
Ref<InputEventKey> k = ev;
547
if (k.is_valid() && k->is_pressed() && k->is_action("ui_menu", true)) {
548
CodeEdit *tx = code_editor->get_text_editor();
549
int line = tx->get_caret_line(0);
550
tx->adjust_viewport_to_caret(0);
551
_make_context_menu(tx->has_selection(0), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->get_caret_draw_pos(0)));
552
context_menu->grab_focus();
553
}
554
}
555
556
void TextEditor::_prepare_edit_menu() {
557
const CodeEdit *tx = code_editor->get_text_editor();
558
PopupMenu *popup = edit_menu->get_popup();
559
popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo());
560
popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo());
561
}
562
563
void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) {
564
context_menu->clear();
565
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_EMOJI_AND_SYMBOL_PICKER)) {
566
context_menu->add_item(TTRC("Emoji & Symbols"), EDIT_EMOJI_AND_SYMBOL);
567
context_menu->add_separator();
568
}
569
if (p_selection) {
570
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
571
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
572
}
573
574
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
575
context_menu->add_separator();
576
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
577
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
578
context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
579
context_menu->add_separator();
580
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
581
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
582
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE);
583
584
if (p_selection) {
585
context_menu->add_separator();
586
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE);
587
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE);
588
}
589
if (p_can_fold || p_is_folded) {
590
context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);
591
}
592
593
const CodeEdit *tx = code_editor->get_text_editor();
594
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo());
595
context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo());
596
597
context_menu->set_position(get_screen_position() + p_position);
598
context_menu->reset_size();
599
context_menu->popup();
600
}
601
602
void TextEditor::update_toggle_files_button() {
603
code_editor->update_toggle_files_button();
604
}
605
606
TextEditor::TextEditor() {
607
code_editor = memnew(CodeTextEditor);
608
add_child(code_editor);
609
code_editor->add_theme_constant_override("separation", 0);
610
code_editor->connect("load_theme_settings", callable_mp(this, &TextEditor::_load_theme_settings));
611
code_editor->connect("validate_script", callable_mp(this, &TextEditor::_validate_script));
612
code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
613
code_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL);
614
code_editor->show_toggle_files_button();
615
code_editor->set_toggle_list_control(ScriptEditor::get_singleton()->get_left_list_split());
616
617
update_settings();
618
619
code_editor->get_text_editor()->set_context_menu_enabled(false);
620
code_editor->get_text_editor()->connect(SceneStringName(gui_input), callable_mp(this, &TextEditor::_text_edit_gui_input));
621
622
context_menu = memnew(PopupMenu);
623
add_child(context_menu);
624
context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_edit_option));
625
626
edit_hb = memnew(HBoxContainer);
627
628
edit_menu = memnew(MenuButton);
629
edit_menu->set_flat(false);
630
edit_menu->set_theme_type_variation("FlatMenuButton");
631
edit_menu->set_shortcut_context(this);
632
edit_hb->add_child(edit_menu);
633
edit_menu->set_text(TTRC("Edit"));
634
edit_menu->set_switch_on_hover(true);
635
edit_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_prepare_edit_menu));
636
edit_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_edit_option));
637
638
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);
639
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);
640
edit_menu->get_popup()->add_separator();
641
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT);
642
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY);
643
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE);
644
edit_menu->get_popup()->add_separator();
645
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all"), EDIT_SELECT_ALL);
646
edit_menu->get_popup()->add_separator();
647
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP);
648
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN);
649
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT);
650
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent"), EDIT_UNINDENT);
651
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE);
652
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);
653
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES);
654
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES);
655
edit_menu->get_popup()->add_separator();
656
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION);
657
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_lines"), EDIT_DUPLICATE_LINES);
658
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap"), EDIT_TOGGLE_WORD_WRAP);
659
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE);
660
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_final_newlines"), EDIT_TRIM_FINAL_NEWLINES);
661
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES);
662
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS);
663
664
edit_menu->get_popup()->add_separator();
665
PopupMenu *convert_case = memnew(PopupMenu);
666
edit_menu->get_popup()->add_submenu_node_item(TTRC("Convert Case"), convert_case);
667
convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase"), EDIT_TO_UPPERCASE);
668
convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase"), EDIT_TO_LOWERCASE);
669
convert_case->add_shortcut(ED_GET_SHORTCUT("script_text_editor/capitalize"), EDIT_CAPITALIZE);
670
convert_case->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_edit_option));
671
672
highlighter_menu = memnew(PopupMenu);
673
edit_menu->get_popup()->add_submenu_node_item(TTRC("Syntax Highlighter"), highlighter_menu);
674
highlighter_menu->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_change_syntax_highlighter));
675
676
Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;
677
plain_highlighter.instantiate();
678
add_syntax_highlighter(plain_highlighter);
679
680
Ref<EditorStandardSyntaxHighlighter> highlighter;
681
highlighter.instantiate();
682
add_syntax_highlighter(highlighter);
683
set_syntax_highlighter(plain_highlighter);
684
685
search_menu = memnew(MenuButton);
686
search_menu->set_flat(false);
687
search_menu->set_theme_type_variation("FlatMenuButton");
688
search_menu->set_shortcut_context(this);
689
edit_hb->add_child(search_menu);
690
search_menu->set_text(TTRC("Search"));
691
search_menu->set_switch_on_hover(true);
692
search_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_edit_option));
693
694
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
695
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
696
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
697
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
698
search_menu->get_popup()->add_separator();
699
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("editor/find_in_files"), SEARCH_IN_FILES);
700
search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);
701
702
MenuButton *goto_menu = memnew(MenuButton);
703
goto_menu->set_flat(false);
704
goto_menu->set_theme_type_variation("FlatMenuButton");
705
goto_menu->set_shortcut_context(this);
706
edit_hb->add_child(goto_menu);
707
goto_menu->set_text(TTRC("Go To"));
708
goto_menu->set_switch_on_hover(true);
709
goto_menu->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &TextEditor::_edit_option));
710
711
goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
712
goto_menu->get_popup()->add_separator();
713
714
bookmarks_menu = memnew(PopupMenu);
715
goto_menu->get_popup()->add_submenu_node_item(TTRC("Bookmarks"), bookmarks_menu);
716
_update_bookmark_list();
717
bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_update_bookmark_list));
718
bookmarks_menu->connect("index_pressed", callable_mp(this, &TextEditor::_bookmark_item_pressed));
719
720
goto_line_popup = memnew(GotoLinePopup);
721
add_child(goto_line_popup);
722
}
723
724
TextEditor::~TextEditor() {
725
highlighters.clear();
726
}
727
728
void TextEditor::validate() {
729
code_editor->validate_script();
730
}
731
732