1 | /**************************************************************************/ |
2 | /* script_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 "script_text_editor.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/math/expression.h" |
35 | #include "core/os/keyboard.h" |
36 | #include "editor/debugger/editor_debugger_node.h" |
37 | #include "editor/editor_command_palette.h" |
38 | #include "editor/editor_node.h" |
39 | #include "editor/editor_scale.h" |
40 | #include "editor/editor_settings.h" |
41 | #include "editor/editor_string_names.h" |
42 | #include "editor/gui/editor_toaster.h" |
43 | #include "scene/gui/rich_text_label.h" |
44 | #include "scene/gui/split_container.h" |
45 | |
46 | void ConnectionInfoDialog::ok_pressed() { |
47 | } |
48 | |
49 | void ConnectionInfoDialog::(String p_method, Vector<Node *> p_nodes) { |
50 | method->set_text(p_method); |
51 | |
52 | tree->clear(); |
53 | TreeItem *root = tree->create_item(); |
54 | |
55 | for (int i = 0; i < p_nodes.size(); i++) { |
56 | List<Connection> all_connections; |
57 | p_nodes[i]->get_signals_connected_to_this(&all_connections); |
58 | |
59 | for (const Connection &connection : all_connections) { |
60 | if (connection.callable.get_method() != p_method) { |
61 | continue; |
62 | } |
63 | |
64 | TreeItem *node_item = tree->create_item(root); |
65 | |
66 | node_item->set_text(0, Object::cast_to<Node>(connection.signal.get_object())->get_name()); |
67 | node_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(connection.signal.get_object(), "Node" )); |
68 | node_item->set_selectable(0, false); |
69 | node_item->set_editable(0, false); |
70 | |
71 | node_item->set_text(1, connection.signal.get_name()); |
72 | Control *p = Object::cast_to<Control>(get_parent()); |
73 | node_item->set_icon(1, p->get_editor_theme_icon(SNAME("Slot" ))); |
74 | node_item->set_selectable(1, false); |
75 | node_item->set_editable(1, false); |
76 | |
77 | node_item->set_text(2, Object::cast_to<Node>(connection.callable.get_object())->get_name()); |
78 | node_item->set_icon(2, EditorNode::get_singleton()->get_object_icon(connection.callable.get_object(), "Node" )); |
79 | node_item->set_selectable(2, false); |
80 | node_item->set_editable(2, false); |
81 | } |
82 | } |
83 | |
84 | popup_centered(Size2(600, 300) * EDSCALE); |
85 | } |
86 | |
87 | ConnectionInfoDialog::ConnectionInfoDialog() { |
88 | set_title(TTR("Connections to method:" )); |
89 | |
90 | VBoxContainer *vbc = memnew(VBoxContainer); |
91 | vbc->set_anchor_and_offset(SIDE_LEFT, Control::ANCHOR_BEGIN, 8 * EDSCALE); |
92 | vbc->set_anchor_and_offset(SIDE_TOP, Control::ANCHOR_BEGIN, 8 * EDSCALE); |
93 | vbc->set_anchor_and_offset(SIDE_RIGHT, Control::ANCHOR_END, -8 * EDSCALE); |
94 | vbc->set_anchor_and_offset(SIDE_BOTTOM, Control::ANCHOR_END, -8 * EDSCALE); |
95 | add_child(vbc); |
96 | |
97 | method = memnew(Label); |
98 | method->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
99 | vbc->add_child(method); |
100 | |
101 | tree = memnew(Tree); |
102 | tree->set_columns(3); |
103 | tree->set_hide_root(true); |
104 | tree->set_column_titles_visible(true); |
105 | tree->set_column_title(0, TTR("Source" )); |
106 | tree->set_column_title(1, TTR("Signal" )); |
107 | tree->set_column_title(2, TTR("Target" )); |
108 | vbc->add_child(tree); |
109 | tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
110 | tree->set_allow_rmb_select(true); |
111 | } |
112 | |
113 | //////////////////////////////////////////////////////////////////////////////// |
114 | |
115 | Vector<String> ScriptTextEditor::get_functions() { |
116 | CodeEdit *te = code_editor->get_text_editor(); |
117 | String text = te->get_text(); |
118 | List<String> fnc; |
119 | |
120 | if (script->get_language()->validate(text, script->get_path(), &fnc)) { |
121 | //if valid rewrite functions to latest |
122 | functions.clear(); |
123 | for (const String &E : fnc) { |
124 | functions.push_back(E); |
125 | } |
126 | } |
127 | |
128 | return functions; |
129 | } |
130 | |
131 | void ScriptTextEditor::apply_code() { |
132 | if (script.is_null()) { |
133 | return; |
134 | } |
135 | script->set_source_code(code_editor->get_text_editor()->get_text()); |
136 | script->update_exports(); |
137 | code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); |
138 | } |
139 | |
140 | Ref<Resource> ScriptTextEditor::get_edited_resource() const { |
141 | return script; |
142 | } |
143 | |
144 | void ScriptTextEditor::set_edited_resource(const Ref<Resource> &p_res) { |
145 | ERR_FAIL_COND(script.is_valid()); |
146 | ERR_FAIL_COND(p_res.is_null()); |
147 | |
148 | script = p_res; |
149 | |
150 | code_editor->get_text_editor()->set_text(script->get_source_code()); |
151 | code_editor->get_text_editor()->clear_undo_history(); |
152 | code_editor->get_text_editor()->tag_saved_version(); |
153 | |
154 | emit_signal(SNAME("name_changed" )); |
155 | code_editor->update_line_and_column(); |
156 | } |
157 | |
158 | void ScriptTextEditor::enable_editor(Control *p_shortcut_context) { |
159 | if (editor_enabled) { |
160 | return; |
161 | } |
162 | |
163 | editor_enabled = true; |
164 | |
165 | _enable_code_editor(); |
166 | |
167 | _validate_script(); |
168 | |
169 | if (p_shortcut_context) { |
170 | for (int i = 0; i < edit_hb->get_child_count(); ++i) { |
171 | Control *c = cast_to<Control>(edit_hb->get_child(i)); |
172 | if (c) { |
173 | c->set_shortcut_context(p_shortcut_context); |
174 | } |
175 | } |
176 | } |
177 | } |
178 | |
179 | void ScriptTextEditor::_load_theme_settings() { |
180 | CodeEdit *text_edit = code_editor->get_text_editor(); |
181 | |
182 | Color updated_marked_line_color = EDITOR_GET("text_editor/theme/highlighting/mark_color" ); |
183 | Color updated_safe_line_number_color = EDITOR_GET("text_editor/theme/highlighting/safe_line_number_color" ); |
184 | Color updated_folded_code_region_color = EDITOR_GET("text_editor/theme/highlighting/folded_code_region_color" ); |
185 | |
186 | bool safe_line_number_color_updated = updated_safe_line_number_color != safe_line_number_color; |
187 | bool marked_line_color_updated = updated_marked_line_color != marked_line_color; |
188 | bool folded_code_region_color_updated = updated_folded_code_region_color != folded_code_region_color; |
189 | if (safe_line_number_color_updated || marked_line_color_updated || folded_code_region_color_updated) { |
190 | safe_line_number_color = updated_safe_line_number_color; |
191 | for (int i = 0; i < text_edit->get_line_count(); i++) { |
192 | if (marked_line_color_updated && text_edit->get_line_background_color(i) == marked_line_color) { |
193 | text_edit->set_line_background_color(i, updated_marked_line_color); |
194 | } |
195 | |
196 | if (safe_line_number_color_updated && text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) { |
197 | text_edit->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); |
198 | } |
199 | |
200 | if (folded_code_region_color_updated && text_edit->get_line_background_color(i) == folded_code_region_color) { |
201 | text_edit->set_line_background_color(i, updated_folded_code_region_color); |
202 | } |
203 | } |
204 | marked_line_color = updated_marked_line_color; |
205 | folded_code_region_color = updated_folded_code_region_color; |
206 | } |
207 | |
208 | theme_loaded = true; |
209 | if (!script.is_null()) { |
210 | _set_theme_for_script(); |
211 | } |
212 | } |
213 | |
214 | void ScriptTextEditor::_set_theme_for_script() { |
215 | if (!theme_loaded) { |
216 | return; |
217 | } |
218 | |
219 | CodeEdit *text_edit = code_editor->get_text_editor(); |
220 | text_edit->get_syntax_highlighter()->update_cache(); |
221 | |
222 | List<String> strings; |
223 | script->get_language()->get_string_delimiters(&strings); |
224 | text_edit->clear_string_delimiters(); |
225 | for (const String &string : strings) { |
226 | String beg = string.get_slice(" " , 0); |
227 | String end = string.get_slice_count(" " ) > 1 ? string.get_slice(" " , 1) : String(); |
228 | if (!text_edit->has_string_delimiter(beg)) { |
229 | text_edit->add_string_delimiter(beg, end, end.is_empty()); |
230 | } |
231 | |
232 | if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { |
233 | text_edit->add_auto_brace_completion_pair(beg, end); |
234 | } |
235 | } |
236 | |
237 | List<String> ; |
238 | script->get_language()->get_comment_delimiters(&comments); |
239 | text_edit->clear_comment_delimiters(); |
240 | for (const String & : comments) { |
241 | String beg = comment.get_slice(" " , 0); |
242 | String end = comment.get_slice_count(" " ) > 1 ? comment.get_slice(" " , 1) : String(); |
243 | text_edit->add_comment_delimiter(beg, end, end.is_empty()); |
244 | |
245 | if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { |
246 | text_edit->add_auto_brace_completion_pair(beg, end); |
247 | } |
248 | } |
249 | } |
250 | |
251 | void ScriptTextEditor::_show_errors_panel(bool p_show) { |
252 | errors_panel->set_visible(p_show); |
253 | } |
254 | |
255 | void ScriptTextEditor::_show_warnings_panel(bool p_show) { |
256 | warnings_panel->set_visible(p_show); |
257 | } |
258 | |
259 | void ScriptTextEditor::_warning_clicked(Variant p_line) { |
260 | if (p_line.get_type() == Variant::INT) { |
261 | goto_line_centered(p_line.operator int64_t()); |
262 | } else if (p_line.get_type() == Variant::DICTIONARY) { |
263 | Dictionary meta = p_line.operator Dictionary(); |
264 | const int line = meta["line" ].operator int64_t() - 1; |
265 | const String code = meta["code" ].operator String(); |
266 | const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes" ) ? "'" : "\"" ; |
267 | |
268 | CodeEdit *text_editor = code_editor->get_text_editor(); |
269 | String prev_line = line > 0 ? text_editor->get_line(line - 1) : "" ; |
270 | if (prev_line.contains("@warning_ignore" )) { |
271 | const int closing_bracket_idx = prev_line.find(")" ); |
272 | const String text_to_insert = ", " + code.quote(quote_style); |
273 | prev_line = prev_line.insert(closing_bracket_idx, text_to_insert); |
274 | text_editor->set_line(line - 1, prev_line); |
275 | } else { |
276 | const int indent = text_editor->get_indent_level(line) / text_editor->get_indent_size(); |
277 | String annotation_indent; |
278 | if (!text_editor->is_indent_using_spaces()) { |
279 | annotation_indent = String("\t" ).repeat(indent); |
280 | } else { |
281 | annotation_indent = String(" " ).repeat(text_editor->get_indent_size() * indent); |
282 | } |
283 | text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")" ); |
284 | } |
285 | |
286 | _validate_script(); |
287 | } |
288 | } |
289 | |
290 | void ScriptTextEditor::_error_clicked(Variant p_line) { |
291 | if (p_line.get_type() == Variant::INT) { |
292 | code_editor->get_text_editor()->remove_secondary_carets(); |
293 | code_editor->get_text_editor()->set_caret_line(p_line.operator int64_t()); |
294 | } else if (p_line.get_type() == Variant::DICTIONARY) { |
295 | Dictionary meta = p_line.operator Dictionary(); |
296 | const String path = meta["path" ].operator String(); |
297 | const int line = meta["line" ].operator int64_t(); |
298 | const int column = meta["column" ].operator int64_t(); |
299 | if (path.is_empty()) { |
300 | code_editor->get_text_editor()->remove_secondary_carets(); |
301 | code_editor->get_text_editor()->set_caret_line(line); |
302 | } else { |
303 | Ref<Resource> scr = ResourceLoader::load(path); |
304 | if (!scr.is_valid()) { |
305 | EditorNode::get_singleton()->show_warning(TTR("Could not load file at:" ) + "\n\n" + path, TTR("Error!" )); |
306 | } else { |
307 | ScriptEditor::get_singleton()->edit(scr, line, column); |
308 | } |
309 | } |
310 | } |
311 | } |
312 | |
313 | void ScriptTextEditor::reload_text() { |
314 | ERR_FAIL_COND(script.is_null()); |
315 | |
316 | CodeEdit *te = code_editor->get_text_editor(); |
317 | int column = te->get_caret_column(); |
318 | int row = te->get_caret_line(); |
319 | int h = te->get_h_scroll(); |
320 | int v = te->get_v_scroll(); |
321 | |
322 | te->set_text(script->get_source_code()); |
323 | te->set_caret_line(row); |
324 | te->set_caret_column(column); |
325 | te->set_h_scroll(h); |
326 | te->set_v_scroll(v); |
327 | |
328 | te->tag_saved_version(); |
329 | |
330 | code_editor->update_line_and_column(); |
331 | _validate_script(); |
332 | } |
333 | |
334 | void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) { |
335 | String code = code_editor->get_text_editor()->get_text(); |
336 | int pos = script->get_language()->find_function(p_function, code); |
337 | code_editor->get_text_editor()->remove_secondary_carets(); |
338 | if (pos == -1) { |
339 | //does not exist |
340 | code_editor->get_text_editor()->deselect(); |
341 | pos = code_editor->get_text_editor()->get_line_count() + 2; |
342 | String func = script->get_language()->make_function("" , p_function, p_args); |
343 | //code=code+func; |
344 | code_editor->get_text_editor()->set_caret_line(pos + 1); |
345 | code_editor->get_text_editor()->set_caret_column(1000000); //none shall be that big |
346 | code_editor->get_text_editor()->insert_text_at_caret("\n\n" + func); |
347 | } |
348 | code_editor->get_text_editor()->set_caret_line(pos); |
349 | code_editor->get_text_editor()->set_caret_column(1); |
350 | } |
351 | |
352 | bool ScriptTextEditor::show_members_overview() { |
353 | return true; |
354 | } |
355 | |
356 | void ScriptTextEditor::update_settings() { |
357 | code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EDITOR_GET("text_editor/appearance/gutters/show_info_gutter" )); |
358 | code_editor->update_editor_settings(); |
359 | } |
360 | |
361 | bool ScriptTextEditor::is_unsaved() { |
362 | const bool unsaved = |
363 | code_editor->get_text_editor()->get_version() != code_editor->get_text_editor()->get_saved_version() || |
364 | script->get_path().is_empty(); // In memory. |
365 | return unsaved; |
366 | } |
367 | |
368 | Variant ScriptTextEditor::get_edit_state() { |
369 | return code_editor->get_edit_state(); |
370 | } |
371 | |
372 | void ScriptTextEditor::set_edit_state(const Variant &p_state) { |
373 | code_editor->set_edit_state(p_state); |
374 | |
375 | Dictionary state = p_state; |
376 | if (state.has("syntax_highlighter" )) { |
377 | int idx = highlighter_menu->get_item_idx_from_text(state["syntax_highlighter" ]); |
378 | if (idx >= 0) { |
379 | _change_syntax_highlighter(idx); |
380 | } |
381 | } |
382 | |
383 | if (editor_enabled) { |
384 | #ifndef ANDROID_ENABLED |
385 | ensure_focus(); |
386 | #endif |
387 | } |
388 | } |
389 | |
390 | Variant ScriptTextEditor::get_navigation_state() { |
391 | return code_editor->get_navigation_state(); |
392 | } |
393 | |
394 | void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { |
395 | code_editor->convert_case(p_case); |
396 | } |
397 | |
398 | void ScriptTextEditor::trim_trailing_whitespace() { |
399 | code_editor->trim_trailing_whitespace(); |
400 | } |
401 | |
402 | void ScriptTextEditor::insert_final_newline() { |
403 | code_editor->insert_final_newline(); |
404 | } |
405 | |
406 | void ScriptTextEditor::convert_indent() { |
407 | code_editor->get_text_editor()->convert_indent(); |
408 | } |
409 | |
410 | void ScriptTextEditor::tag_saved_version() { |
411 | code_editor->get_text_editor()->tag_saved_version(); |
412 | } |
413 | |
414 | void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { |
415 | code_editor->goto_line(p_line); |
416 | } |
417 | |
418 | void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { |
419 | code_editor->goto_line_selection(p_line, p_begin, p_end); |
420 | } |
421 | |
422 | void ScriptTextEditor::goto_line_centered(int p_line) { |
423 | code_editor->goto_line_centered(p_line); |
424 | } |
425 | |
426 | void ScriptTextEditor::set_executing_line(int p_line) { |
427 | code_editor->set_executing_line(p_line); |
428 | } |
429 | |
430 | void ScriptTextEditor::clear_executing_line() { |
431 | code_editor->clear_executing_line(); |
432 | } |
433 | |
434 | void ScriptTextEditor::ensure_focus() { |
435 | code_editor->get_text_editor()->grab_focus(); |
436 | } |
437 | |
438 | String ScriptTextEditor::get_name() { |
439 | String name; |
440 | |
441 | name = script->get_path().get_file(); |
442 | if (name.is_empty()) { |
443 | // This appears for newly created built-in scripts before saving the scene. |
444 | name = TTR("[unsaved]" ); |
445 | } else if (script->is_built_in()) { |
446 | const String &script_name = script->get_name(); |
447 | if (!script_name.is_empty()) { |
448 | // If the built-in script has a custom resource name defined, |
449 | // display the built-in script name as follows: `ResourceName (scene_file.tscn)` |
450 | name = vformat("%s (%s)" , script_name, name.get_slice("::" , 0)); |
451 | } |
452 | } |
453 | |
454 | if (is_unsaved()) { |
455 | name += "(*)" ; |
456 | } |
457 | |
458 | return name; |
459 | } |
460 | |
461 | Ref<Texture2D> ScriptTextEditor::get_theme_icon() { |
462 | if (get_parent_control()) { |
463 | String icon_name = script->get_class(); |
464 | if (script->is_built_in()) { |
465 | icon_name += "Internal" ; |
466 | } |
467 | |
468 | if (get_parent_control()->has_theme_icon(icon_name, EditorStringName(EditorIcons))) { |
469 | return get_parent_control()->get_editor_theme_icon(icon_name); |
470 | } else if (get_parent_control()->has_theme_icon(script->get_class(), EditorStringName(EditorIcons))) { |
471 | return get_parent_control()->get_editor_theme_icon(script->get_class()); |
472 | } |
473 | } |
474 | |
475 | return Ref<Texture2D>(); |
476 | } |
477 | |
478 | void ScriptTextEditor::_validate_script() { |
479 | CodeEdit *te = code_editor->get_text_editor(); |
480 | |
481 | String text = te->get_text(); |
482 | List<String> fnc; |
483 | |
484 | warnings.clear(); |
485 | errors.clear(); |
486 | depended_errors.clear(); |
487 | safe_lines.clear(); |
488 | |
489 | if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) { |
490 | for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) { |
491 | if ((E->get().path.is_empty() && !script->get_path().is_empty()) || E->get().path != script->get_path()) { |
492 | depended_errors[E->get().path].push_back(E->get()); |
493 | E->erase(); |
494 | } |
495 | } |
496 | |
497 | if (errors.size() > 0) { |
498 | // TRANSLATORS: Script error pointing to a line and column number. |
499 | String error_text = vformat(TTR("Error at (%d, %d):" ), errors[0].line, errors[0].column) + " " + errors[0].message; |
500 | code_editor->set_error(error_text); |
501 | code_editor->set_error_pos(errors[0].line - 1, errors[0].column - 1); |
502 | } |
503 | script_is_valid = false; |
504 | } else { |
505 | code_editor->set_error("" ); |
506 | if (!script->is_tool()) { |
507 | script->set_source_code(text); |
508 | script->update_exports(); |
509 | te->get_syntax_highlighter()->update_cache(); |
510 | } |
511 | |
512 | functions.clear(); |
513 | for (const String &E : fnc) { |
514 | functions.push_back(E); |
515 | } |
516 | script_is_valid = true; |
517 | } |
518 | _update_connected_methods(); |
519 | _update_warnings(); |
520 | _update_errors(); |
521 | |
522 | emit_signal(SNAME("name_changed" )); |
523 | emit_signal(SNAME("edited_script_changed" )); |
524 | } |
525 | |
526 | void ScriptTextEditor::_update_warnings() { |
527 | int warning_nb = warnings.size(); |
528 | warnings_panel->clear(); |
529 | |
530 | bool has_connections_table = false; |
531 | // Add missing connections. |
532 | if (GLOBAL_GET("debug/gdscript/warnings/enable" ).booleanize()) { |
533 | Node *base = get_tree()->get_edited_scene_root(); |
534 | if (base && missing_connections.size() > 0) { |
535 | has_connections_table = true; |
536 | warnings_panel->push_table(1); |
537 | for (const Connection &connection : missing_connections) { |
538 | String base_path = base->get_name(); |
539 | String source_path = base == connection.signal.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to<Node>(connection.signal.get_object())); |
540 | String target_path = base == connection.callable.get_object() ? base_path : base_path + "/" + base->get_path_to(Object::cast_to<Node>(connection.callable.get_object())); |
541 | |
542 | warnings_panel->push_cell(); |
543 | warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color" ), EditorStringName(Editor))); |
544 | warnings_panel->add_text(vformat(TTR("Missing connected method '%s' for signal '%s' from node '%s' to node '%s'." ), connection.callable.get_method(), connection.signal.get_name(), source_path, target_path)); |
545 | warnings_panel->pop(); // Color. |
546 | warnings_panel->pop(); // Cell. |
547 | } |
548 | warnings_panel->pop(); // Table. |
549 | |
550 | warning_nb += missing_connections.size(); |
551 | } |
552 | } |
553 | |
554 | code_editor->set_warning_count(warning_nb); |
555 | |
556 | if (has_connections_table) { |
557 | warnings_panel->add_newline(); |
558 | } |
559 | |
560 | // Add script warnings. |
561 | warnings_panel->push_table(3); |
562 | for (const ScriptLanguage::Warning &w : warnings) { |
563 | Dictionary ignore_meta; |
564 | ignore_meta["line" ] = w.start_line; |
565 | ignore_meta["code" ] = w.string_code.to_lower(); |
566 | warnings_panel->push_cell(); |
567 | warnings_panel->push_meta(ignore_meta); |
568 | warnings_panel->push_color( |
569 | warnings_panel->get_theme_color(SNAME("accent_color" ), EditorStringName(Editor)).lerp(warnings_panel->get_theme_color(SNAME("mono_color" ), EditorStringName(Editor)), 0.5f)); |
570 | warnings_panel->add_text(TTR("[Ignore]" )); |
571 | warnings_panel->pop(); // Color. |
572 | warnings_panel->pop(); // Meta ignore. |
573 | warnings_panel->pop(); // Cell. |
574 | |
575 | warnings_panel->push_cell(); |
576 | warnings_panel->push_meta(w.start_line - 1); |
577 | warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color" ), EditorStringName(Editor))); |
578 | warnings_panel->add_text(TTR("Line" ) + " " + itos(w.start_line)); |
579 | warnings_panel->add_text(" (" + w.string_code + "):" ); |
580 | warnings_panel->pop(); // Color. |
581 | warnings_panel->pop(); // Meta goto. |
582 | warnings_panel->pop(); // Cell. |
583 | |
584 | warnings_panel->push_cell(); |
585 | warnings_panel->add_text(w.message); |
586 | warnings_panel->add_newline(); |
587 | warnings_panel->pop(); // Cell. |
588 | } |
589 | warnings_panel->pop(); // Table. |
590 | } |
591 | |
592 | void ScriptTextEditor::_update_errors() { |
593 | code_editor->set_error_count(errors.size()); |
594 | |
595 | errors_panel->clear(); |
596 | errors_panel->push_table(2); |
597 | for (const ScriptLanguage::ScriptError &err : errors) { |
598 | Dictionary click_meta; |
599 | click_meta["line" ] = err.line; |
600 | click_meta["column" ] = err.column; |
601 | |
602 | errors_panel->push_cell(); |
603 | errors_panel->push_meta(err.line - 1); |
604 | errors_panel->push_color(warnings_panel->get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
605 | errors_panel->add_text(TTR("Line" ) + " " + itos(err.line) + ":" ); |
606 | errors_panel->pop(); // Color. |
607 | errors_panel->pop(); // Meta goto. |
608 | errors_panel->pop(); // Cell. |
609 | |
610 | errors_panel->push_cell(); |
611 | errors_panel->add_text(err.message); |
612 | errors_panel->add_newline(); |
613 | errors_panel->pop(); // Cell. |
614 | } |
615 | errors_panel->pop(); // Table |
616 | |
617 | for (const KeyValue<String, List<ScriptLanguage::ScriptError>> &KV : depended_errors) { |
618 | Dictionary click_meta; |
619 | click_meta["path" ] = KV.key; |
620 | click_meta["line" ] = 1; |
621 | |
622 | errors_panel->add_newline(); |
623 | errors_panel->add_newline(); |
624 | errors_panel->push_meta(click_meta); |
625 | errors_panel->add_text(vformat(R"(%s:)" , KV.key)); |
626 | errors_panel->pop(); // Meta goto. |
627 | errors_panel->add_newline(); |
628 | |
629 | errors_panel->push_indent(1); |
630 | errors_panel->push_table(2); |
631 | String filename = KV.key.get_file(); |
632 | for (const ScriptLanguage::ScriptError &err : KV.value) { |
633 | click_meta["line" ] = err.line; |
634 | click_meta["column" ] = err.column; |
635 | |
636 | errors_panel->push_cell(); |
637 | errors_panel->push_meta(click_meta); |
638 | errors_panel->push_color(errors_panel->get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
639 | errors_panel->add_text(TTR("Line" ) + " " + itos(err.line) + ":" ); |
640 | errors_panel->pop(); // Color. |
641 | errors_panel->pop(); // Meta goto. |
642 | errors_panel->pop(); // Cell. |
643 | |
644 | errors_panel->push_cell(); |
645 | errors_panel->add_text(err.message); |
646 | errors_panel->pop(); // Cell. |
647 | } |
648 | errors_panel->pop(); // Table |
649 | errors_panel->pop(); // Indent. |
650 | } |
651 | |
652 | CodeEdit *te = code_editor->get_text_editor(); |
653 | bool highlight_safe = EDITOR_GET("text_editor/appearance/gutters/highlight_type_safe_lines" ); |
654 | bool last_is_safe = false; |
655 | for (int i = 0; i < te->get_line_count(); i++) { |
656 | if (errors.is_empty()) { |
657 | bool is_folded_code_region = te->is_line_code_region_start(i) && te->is_line_folded(i); |
658 | te->set_line_background_color(i, is_folded_code_region ? folded_code_region_color : Color(0, 0, 0, 0)); |
659 | } else { |
660 | for (const ScriptLanguage::ScriptError &E : errors) { |
661 | bool error_line = i == E.line - 1; |
662 | te->set_line_background_color(i, error_line ? marked_line_color : Color(0, 0, 0, 0)); |
663 | if (error_line) { |
664 | break; |
665 | } |
666 | } |
667 | } |
668 | |
669 | if (highlight_safe) { |
670 | if (safe_lines.has(i + 1)) { |
671 | te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); |
672 | last_is_safe = true; |
673 | } else if (last_is_safe && (te->is_in_comment(i) != -1 || te->get_line(i).strip_edges().is_empty())) { |
674 | te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); |
675 | } else { |
676 | te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color); |
677 | last_is_safe = false; |
678 | } |
679 | } else { |
680 | te->set_line_gutter_item_color(i, 1, default_line_number_color); |
681 | } |
682 | } |
683 | } |
684 | |
685 | void ScriptTextEditor::_update_bookmark_list() { |
686 | bookmarks_menu->clear(); |
687 | bookmarks_menu->reset_size(); |
688 | |
689 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark" ), BOOKMARK_TOGGLE); |
690 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks" ), BOOKMARK_REMOVE_ALL); |
691 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_bookmark" ), BOOKMARK_GOTO_NEXT); |
692 | bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_bookmark" ), BOOKMARK_GOTO_PREV); |
693 | |
694 | PackedInt32Array bookmark_list = code_editor->get_text_editor()->get_bookmarked_lines(); |
695 | if (bookmark_list.size() == 0) { |
696 | return; |
697 | } |
698 | |
699 | bookmarks_menu->add_separator(); |
700 | |
701 | for (int i = 0; i < bookmark_list.size(); i++) { |
702 | // Strip edges to remove spaces or tabs. |
703 | // Also replace any tabs by spaces, since we can't print tabs in the menu. |
704 | String line = code_editor->get_text_editor()->get_line(bookmark_list[i]).replace("\t" , " " ).strip_edges(); |
705 | |
706 | // Limit the size of the line if too big. |
707 | if (line.length() > 50) { |
708 | line = line.substr(0, 50); |
709 | } |
710 | |
711 | bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - `" + line + "`" ); |
712 | bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); |
713 | } |
714 | } |
715 | |
716 | void ScriptTextEditor::_bookmark_item_pressed(int p_idx) { |
717 | if (p_idx < 4) { // Any item before the separator. |
718 | _edit_option(bookmarks_menu->get_item_id(p_idx)); |
719 | } else { |
720 | code_editor->goto_line_centered(bookmarks_menu->get_item_metadata(p_idx)); |
721 | } |
722 | } |
723 | |
724 | static Vector<Node *> _find_all_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) { |
725 | Vector<Node *> nodes; |
726 | |
727 | if (p_current->get_owner() != p_base && p_base != p_current) { |
728 | return nodes; |
729 | } |
730 | |
731 | Ref<Script> c = p_current->get_script(); |
732 | if (c == p_script) { |
733 | nodes.push_back(p_current); |
734 | } |
735 | |
736 | for (int i = 0; i < p_current->get_child_count(); i++) { |
737 | Vector<Node *> found = _find_all_node_for_script(p_base, p_current->get_child(i), p_script); |
738 | nodes.append_array(found); |
739 | } |
740 | |
741 | return nodes; |
742 | } |
743 | |
744 | static Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) { |
745 | if (p_current->get_owner() != p_base && p_base != p_current) { |
746 | return nullptr; |
747 | } |
748 | Ref<Script> c = p_current->get_script(); |
749 | if (c == p_script) { |
750 | return p_current; |
751 | } |
752 | for (int i = 0; i < p_current->get_child_count(); i++) { |
753 | Node *found = _find_node_for_script(p_base, p_current->get_child(i), p_script); |
754 | if (found) { |
755 | return found; |
756 | } |
757 | } |
758 | |
759 | return nullptr; |
760 | } |
761 | |
762 | static void _find_changed_scripts_for_external_editor(Node *p_base, Node *p_current, HashSet<Ref<Script>> &r_scripts) { |
763 | if (p_current->get_owner() != p_base && p_base != p_current) { |
764 | return; |
765 | } |
766 | Ref<Script> c = p_current->get_script(); |
767 | |
768 | if (c.is_valid()) { |
769 | r_scripts.insert(c); |
770 | } |
771 | |
772 | for (int i = 0; i < p_current->get_child_count(); i++) { |
773 | _find_changed_scripts_for_external_editor(p_base, p_current->get_child(i), r_scripts); |
774 | } |
775 | } |
776 | |
777 | void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_for_script) { |
778 | if (!bool(EDITOR_GET("text_editor/external/use_external_editor" ))) { |
779 | return; |
780 | } |
781 | |
782 | ERR_FAIL_COND(!get_tree()); |
783 | |
784 | HashSet<Ref<Script>> scripts; |
785 | |
786 | Node *base = get_tree()->get_edited_scene_root(); |
787 | if (base) { |
788 | _find_changed_scripts_for_external_editor(base, base, scripts); |
789 | } |
790 | |
791 | for (const Ref<Script> &E : scripts) { |
792 | Ref<Script> scr = E; |
793 | |
794 | if (p_for_script.is_valid() && p_for_script != scr) { |
795 | continue; |
796 | } |
797 | |
798 | if (scr->is_built_in()) { |
799 | continue; //internal script, who cares, though weird |
800 | } |
801 | |
802 | uint64_t last_date = scr->get_last_modified_time(); |
803 | uint64_t date = FileAccess::get_modified_time(scr->get_path()); |
804 | |
805 | if (last_date != date) { |
806 | Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); |
807 | ERR_CONTINUE(!rel_scr.is_valid()); |
808 | scr->set_source_code(rel_scr->get_source_code()); |
809 | scr->set_last_modified_time(rel_scr->get_last_modified_time()); |
810 | scr->update_exports(); |
811 | |
812 | _trigger_live_script_reload(); |
813 | } |
814 | } |
815 | } |
816 | |
817 | void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { |
818 | ScriptTextEditor *ste = (ScriptTextEditor *)p_ud; |
819 | ste->_code_complete_script(p_code, r_options, r_force); |
820 | } |
821 | |
822 | void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { |
823 | if (color_panel->is_visible()) { |
824 | return; |
825 | } |
826 | Node *base = get_tree()->get_edited_scene_root(); |
827 | if (base) { |
828 | base = _find_node_for_script(base, base, script); |
829 | } |
830 | String hint; |
831 | Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint); |
832 | |
833 | if (err == OK) { |
834 | code_editor->get_text_editor()->set_code_hint(hint); |
835 | } |
836 | } |
837 | |
838 | void ScriptTextEditor::_update_breakpoint_list() { |
839 | breakpoints_menu->clear(); |
840 | breakpoints_menu->reset_size(); |
841 | |
842 | breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_breakpoint" ), DEBUG_TOGGLE_BREAKPOINT); |
843 | breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_breakpoints" ), DEBUG_REMOVE_ALL_BREAKPOINTS); |
844 | breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_next_breakpoint" ), DEBUG_GOTO_NEXT_BREAKPOINT); |
845 | breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_previous_breakpoint" ), DEBUG_GOTO_PREV_BREAKPOINT); |
846 | |
847 | PackedInt32Array breakpoint_list = code_editor->get_text_editor()->get_breakpointed_lines(); |
848 | if (breakpoint_list.size() == 0) { |
849 | return; |
850 | } |
851 | |
852 | breakpoints_menu->add_separator(); |
853 | |
854 | for (int i = 0; i < breakpoint_list.size(); i++) { |
855 | // Strip edges to remove spaces or tabs. |
856 | // Also replace any tabs by spaces, since we can't print tabs in the menu. |
857 | String line = code_editor->get_text_editor()->get_line(breakpoint_list[i]).replace("\t" , " " ).strip_edges(); |
858 | |
859 | // Limit the size of the line if too big. |
860 | if (line.length() > 50) { |
861 | line = line.substr(0, 50); |
862 | } |
863 | |
864 | breakpoints_menu->add_item(String::num((int)breakpoint_list[i] + 1) + " - `" + line + "`" ); |
865 | breakpoints_menu->set_item_metadata(-1, breakpoint_list[i]); |
866 | } |
867 | } |
868 | |
869 | void ScriptTextEditor::_breakpoint_item_pressed(int p_idx) { |
870 | if (p_idx < 4) { // Any item before the separator. |
871 | _edit_option(breakpoints_menu->get_item_id(p_idx)); |
872 | } else { |
873 | code_editor->goto_line(breakpoints_menu->get_item_metadata(p_idx)); |
874 | code_editor->get_text_editor()->call_deferred(SNAME("center_viewport_to_caret" )); //Need to be deferred, because goto uses call_deferred(). |
875 | } |
876 | } |
877 | |
878 | void ScriptTextEditor::_breakpoint_toggled(int p_row) { |
879 | EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_row + 1, code_editor->get_text_editor()->is_line_breakpointed(p_row)); |
880 | } |
881 | |
882 | void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_column) { |
883 | Node *base = get_tree()->get_edited_scene_root(); |
884 | if (base) { |
885 | base = _find_node_for_script(base, base, script); |
886 | } |
887 | |
888 | ScriptLanguage::LookupResult result; |
889 | String code_text = code_editor->get_text_editor()->get_text_with_cursor_char(p_row, p_column); |
890 | Error lc_error = script->get_language()->lookup_code(code_text, p_symbol, script->get_path(), base, result); |
891 | if (ScriptServer::is_global_class(p_symbol)) { |
892 | EditorNode::get_singleton()->load_resource(ScriptServer::get_global_class_path(p_symbol)); |
893 | } else if (p_symbol.is_resource_file()) { |
894 | List<String> scene_extensions; |
895 | ResourceLoader::get_recognized_extensions_for_type("PackedScene" , &scene_extensions); |
896 | |
897 | if (scene_extensions.find(p_symbol.get_extension())) { |
898 | EditorNode::get_singleton()->load_scene(p_symbol); |
899 | } else { |
900 | EditorNode::get_singleton()->load_resource(p_symbol); |
901 | } |
902 | |
903 | } else if (lc_error == OK) { |
904 | _goto_line(p_row); |
905 | |
906 | switch (result.type) { |
907 | case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION: { |
908 | if (result.script.is_valid()) { |
909 | emit_signal(SNAME("request_open_script_at_line" ), result.script, result.location - 1); |
910 | } else { |
911 | emit_signal(SNAME("request_save_history" )); |
912 | goto_line_centered(result.location - 1); |
913 | } |
914 | } break; |
915 | case ScriptLanguage::LOOKUP_RESULT_CLASS: { |
916 | emit_signal(SNAME("go_to_help" ), "class_name:" + result.class_name); |
917 | } break; |
918 | case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: { |
919 | StringName cname = result.class_name; |
920 | bool success; |
921 | while (true) { |
922 | ClassDB::get_integer_constant(cname, result.class_member, &success); |
923 | if (success) { |
924 | result.class_name = cname; |
925 | cname = ClassDB::get_parent_class(cname); |
926 | } else { |
927 | break; |
928 | } |
929 | } |
930 | |
931 | emit_signal(SNAME("go_to_help" ), "class_constant:" + result.class_name + ":" + result.class_member); |
932 | |
933 | } break; |
934 | case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: { |
935 | emit_signal(SNAME("go_to_help" ), "class_property:" + result.class_name + ":" + result.class_member); |
936 | |
937 | } break; |
938 | case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: { |
939 | StringName cname = result.class_name; |
940 | |
941 | while (true) { |
942 | if (ClassDB::has_method(cname, result.class_member)) { |
943 | result.class_name = cname; |
944 | cname = ClassDB::get_parent_class(cname); |
945 | } else { |
946 | break; |
947 | } |
948 | } |
949 | |
950 | emit_signal(SNAME("go_to_help" ), "class_method:" + result.class_name + ":" + result.class_member); |
951 | |
952 | } break; |
953 | case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: { |
954 | StringName cname = result.class_name; |
955 | |
956 | while (true) { |
957 | if (ClassDB::has_signal(cname, result.class_member)) { |
958 | result.class_name = cname; |
959 | cname = ClassDB::get_parent_class(cname); |
960 | } else { |
961 | break; |
962 | } |
963 | } |
964 | |
965 | emit_signal(SNAME("go_to_help" ), "class_signal:" + result.class_name + ":" + result.class_member); |
966 | |
967 | } break; |
968 | case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: { |
969 | StringName cname = result.class_name; |
970 | StringName success; |
971 | while (true) { |
972 | success = ClassDB::get_integer_constant_enum(cname, result.class_member, true); |
973 | if (success != StringName()) { |
974 | result.class_name = cname; |
975 | cname = ClassDB::get_parent_class(cname); |
976 | } else { |
977 | break; |
978 | } |
979 | } |
980 | |
981 | emit_signal(SNAME("go_to_help" ), "class_enum:" + result.class_name + ":" + result.class_member); |
982 | |
983 | } break; |
984 | case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: { |
985 | emit_signal(SNAME("go_to_help" ), "class_annotation:" + result.class_name + ":" + result.class_member); |
986 | } break; |
987 | case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { |
988 | emit_signal(SNAME("go_to_help" ), "class_global:" + result.class_name + ":" + result.class_member); |
989 | } break; |
990 | default: { |
991 | } |
992 | } |
993 | } else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { |
994 | // Check for Autoload scenes. |
995 | const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(p_symbol); |
996 | if (info.is_singleton) { |
997 | EditorNode::get_singleton()->load_scene(info.path); |
998 | } |
999 | } else if (p_symbol.is_relative_path()) { |
1000 | // Every symbol other than absolute path is relative path so keep this condition at last. |
1001 | String path = _get_absolute_path(p_symbol); |
1002 | if (FileAccess::exists(path)) { |
1003 | List<String> scene_extensions; |
1004 | ResourceLoader::get_recognized_extensions_for_type("PackedScene" , &scene_extensions); |
1005 | |
1006 | if (scene_extensions.find(path.get_extension())) { |
1007 | EditorNode::get_singleton()->load_scene(path); |
1008 | } else { |
1009 | EditorNode::get_singleton()->load_resource(path); |
1010 | } |
1011 | } |
1012 | } |
1013 | } |
1014 | |
1015 | void ScriptTextEditor::_validate_symbol(const String &p_symbol) { |
1016 | CodeEdit *text_edit = code_editor->get_text_editor(); |
1017 | |
1018 | Node *base = get_tree()->get_edited_scene_root(); |
1019 | if (base) { |
1020 | base = _find_node_for_script(base, base, script); |
1021 | } |
1022 | |
1023 | ScriptLanguage::LookupResult result; |
1024 | String lc_text = code_editor->get_text_editor()->get_text_for_symbol_lookup(); |
1025 | Error lc_error = script->get_language()->lookup_code(lc_text, p_symbol, script->get_path(), base, result); |
1026 | bool is_singleton = ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton; |
1027 | if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || lc_error == OK || is_singleton) { |
1028 | text_edit->set_symbol_lookup_word_as_valid(true); |
1029 | } else if (p_symbol.is_relative_path()) { |
1030 | String path = _get_absolute_path(p_symbol); |
1031 | if (FileAccess::exists(path)) { |
1032 | text_edit->set_symbol_lookup_word_as_valid(true); |
1033 | } else { |
1034 | text_edit->set_symbol_lookup_word_as_valid(false); |
1035 | } |
1036 | } else { |
1037 | text_edit->set_symbol_lookup_word_as_valid(false); |
1038 | } |
1039 | } |
1040 | |
1041 | String ScriptTextEditor::_get_absolute_path(const String &rel_path) { |
1042 | String base_path = script->get_path().get_base_dir(); |
1043 | String path = base_path.path_join(rel_path); |
1044 | return path.replace("///" , "//" ).simplify_path(); |
1045 | } |
1046 | |
1047 | void ScriptTextEditor::update_toggle_scripts_button() { |
1048 | code_editor->update_toggle_scripts_button(); |
1049 | } |
1050 | |
1051 | void ScriptTextEditor::_update_connected_methods() { |
1052 | CodeEdit *text_edit = code_editor->get_text_editor(); |
1053 | text_edit->set_gutter_width(connection_gutter, text_edit->get_line_height()); |
1054 | for (int i = 0; i < text_edit->get_line_count(); i++) { |
1055 | text_edit->set_line_gutter_metadata(i, connection_gutter, Dictionary()); |
1056 | text_edit->set_line_gutter_icon(i, connection_gutter, nullptr); |
1057 | text_edit->set_line_gutter_clickable(i, connection_gutter, false); |
1058 | } |
1059 | missing_connections.clear(); |
1060 | |
1061 | if (!script_is_valid) { |
1062 | return; |
1063 | } |
1064 | |
1065 | Node *base = get_tree()->get_edited_scene_root(); |
1066 | if (!base) { |
1067 | return; |
1068 | } |
1069 | |
1070 | // Add connection icons to methods. |
1071 | Vector<Node *> nodes = _find_all_node_for_script(base, base, script); |
1072 | HashSet<StringName> methods_found; |
1073 | for (int i = 0; i < nodes.size(); i++) { |
1074 | List<Connection> signal_connections; |
1075 | nodes[i]->get_signals_connected_to_this(&signal_connections); |
1076 | |
1077 | for (const Connection &connection : signal_connections) { |
1078 | if (!(connection.flags & CONNECT_PERSIST)) { |
1079 | continue; |
1080 | } |
1081 | |
1082 | // As deleted nodes are still accessible via the undo/redo system, check if they're still on the tree. |
1083 | Node *source = Object::cast_to<Node>(connection.signal.get_object()); |
1084 | if (source && !source->is_inside_tree()) { |
1085 | continue; |
1086 | } |
1087 | |
1088 | const StringName method = connection.callable.get_method(); |
1089 | if (methods_found.has(method)) { |
1090 | continue; |
1091 | } |
1092 | |
1093 | if (!ClassDB::has_method(script->get_instance_base_type(), method)) { |
1094 | int line = -1; |
1095 | |
1096 | for (int j = 0; j < functions.size(); j++) { |
1097 | String name = functions[j].get_slice(":" , 0); |
1098 | if (name == method) { |
1099 | Dictionary line_meta; |
1100 | line_meta["type" ] = "connection" ; |
1101 | line_meta["method" ] = method; |
1102 | line = functions[j].get_slice(":" , 1).to_int() - 1; |
1103 | text_edit->set_line_gutter_metadata(line, connection_gutter, line_meta); |
1104 | text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("Slot" ))); |
1105 | text_edit->set_line_gutter_clickable(line, connection_gutter, true); |
1106 | methods_found.insert(method); |
1107 | break; |
1108 | } |
1109 | } |
1110 | |
1111 | if (line >= 0) { |
1112 | continue; |
1113 | } |
1114 | |
1115 | // There is a chance that the method is inherited from another script. |
1116 | bool found_inherited_function = false; |
1117 | Ref<Script> inherited_script = script->get_base_script(); |
1118 | while (!inherited_script.is_null()) { |
1119 | if (inherited_script->has_method(method)) { |
1120 | found_inherited_function = true; |
1121 | break; |
1122 | } |
1123 | |
1124 | inherited_script = inherited_script->get_base_script(); |
1125 | } |
1126 | |
1127 | if (!found_inherited_function) { |
1128 | missing_connections.push_back(connection); |
1129 | } |
1130 | } |
1131 | } |
1132 | } |
1133 | |
1134 | // Add override icons to methods. |
1135 | methods_found.clear(); |
1136 | for (int i = 0; i < functions.size(); i++) { |
1137 | StringName name = StringName(functions[i].get_slice(":" , 0)); |
1138 | if (methods_found.has(name)) { |
1139 | continue; |
1140 | } |
1141 | |
1142 | String found_base_class; |
1143 | StringName base_class = script->get_instance_base_type(); |
1144 | Ref<Script> inherited_script = script->get_base_script(); |
1145 | while (!inherited_script.is_null()) { |
1146 | if (inherited_script->has_method(name)) { |
1147 | found_base_class = "script:" + inherited_script->get_path(); |
1148 | break; |
1149 | } |
1150 | |
1151 | base_class = inherited_script->get_instance_base_type(); |
1152 | inherited_script = inherited_script->get_base_script(); |
1153 | } |
1154 | |
1155 | if (found_base_class.is_empty()) { |
1156 | while (base_class) { |
1157 | List<MethodInfo> methods; |
1158 | ClassDB::get_method_list(base_class, &methods, true); |
1159 | for (int j = 0; j < methods.size(); j++) { |
1160 | if (methods[j].name == name) { |
1161 | found_base_class = "builtin:" + base_class; |
1162 | break; |
1163 | } |
1164 | } |
1165 | |
1166 | ClassDB::ClassInfo *base_class_ptr = ClassDB::classes.getptr(base_class)->inherits_ptr; |
1167 | if (base_class_ptr == nullptr) { |
1168 | break; |
1169 | } |
1170 | base_class = base_class_ptr->name; |
1171 | } |
1172 | } |
1173 | |
1174 | if (!found_base_class.is_empty()) { |
1175 | int line = functions[i].get_slice(":" , 1).to_int() - 1; |
1176 | |
1177 | Dictionary line_meta = text_edit->get_line_gutter_metadata(line, connection_gutter); |
1178 | if (line_meta.is_empty()) { |
1179 | // Add override icon to gutter. |
1180 | line_meta["type" ] = "inherits" ; |
1181 | line_meta["method" ] = name; |
1182 | line_meta["base_class" ] = found_base_class; |
1183 | text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("MethodOverride" ))); |
1184 | text_edit->set_line_gutter_clickable(line, connection_gutter, true); |
1185 | } else { |
1186 | // If method is also connected to signal, then merge icons and keep the click behavior of the slot. |
1187 | text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_editor_theme_icon(SNAME("MethodOverrideAndSlot" ))); |
1188 | } |
1189 | |
1190 | methods_found.insert(name); |
1191 | } |
1192 | } |
1193 | } |
1194 | |
1195 | void ScriptTextEditor::_update_gutter_indexes() { |
1196 | for (int i = 0; i < code_editor->get_text_editor()->get_gutter_count(); i++) { |
1197 | if (code_editor->get_text_editor()->get_gutter_name(i) == "connection_gutter" ) { |
1198 | connection_gutter = i; |
1199 | continue; |
1200 | } |
1201 | |
1202 | if (code_editor->get_text_editor()->get_gutter_name(i) == "line_numbers" ) { |
1203 | line_number_gutter = i; |
1204 | continue; |
1205 | } |
1206 | } |
1207 | } |
1208 | |
1209 | void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) { |
1210 | if (p_gutter != connection_gutter) { |
1211 | return; |
1212 | } |
1213 | |
1214 | Dictionary meta = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter); |
1215 | String type = meta.get("type" , "" ); |
1216 | if (type.is_empty()) { |
1217 | return; |
1218 | } |
1219 | |
1220 | // All types currently need a method name. |
1221 | String method = meta.get("method" , "" ); |
1222 | if (method.is_empty()) { |
1223 | return; |
1224 | } |
1225 | |
1226 | if (type == "connection" ) { |
1227 | Node *base = get_tree()->get_edited_scene_root(); |
1228 | if (!base) { |
1229 | return; |
1230 | } |
1231 | |
1232 | Vector<Node *> nodes = _find_all_node_for_script(base, base, script); |
1233 | connection_info_dialog->popup_connections(method, nodes); |
1234 | } else if (type == "inherits" ) { |
1235 | String base_class_raw = meta["base_class" ]; |
1236 | PackedStringArray base_class_split = base_class_raw.split(":" , true, 1); |
1237 | |
1238 | if (base_class_split[0] == "script" ) { |
1239 | // Go to function declaration. |
1240 | Ref<Script> base_script = ResourceLoader::load(base_class_split[1]); |
1241 | ERR_FAIL_COND(!base_script.is_valid()); |
1242 | emit_signal(SNAME("go_to_method" ), base_script, method); |
1243 | } else if (base_class_split[0] == "builtin" ) { |
1244 | // Open method documentation. |
1245 | emit_signal(SNAME("go_to_help" ), "class_method:" + base_class_split[1] + ":" + method); |
1246 | } |
1247 | } |
1248 | } |
1249 | |
1250 | void ScriptTextEditor::_edit_option(int p_op) { |
1251 | CodeEdit *tx = code_editor->get_text_editor(); |
1252 | |
1253 | switch (p_op) { |
1254 | case EDIT_UNDO: { |
1255 | tx->undo(); |
1256 | tx->call_deferred(SNAME("grab_focus" )); |
1257 | } break; |
1258 | case EDIT_REDO: { |
1259 | tx->redo(); |
1260 | tx->call_deferred(SNAME("grab_focus" )); |
1261 | } break; |
1262 | case EDIT_CUT: { |
1263 | tx->cut(); |
1264 | tx->call_deferred(SNAME("grab_focus" )); |
1265 | } break; |
1266 | case EDIT_COPY: { |
1267 | tx->copy(); |
1268 | tx->call_deferred(SNAME("grab_focus" )); |
1269 | } break; |
1270 | case EDIT_PASTE: { |
1271 | tx->paste(); |
1272 | tx->call_deferred(SNAME("grab_focus" )); |
1273 | } break; |
1274 | case EDIT_SELECT_ALL: { |
1275 | tx->select_all(); |
1276 | tx->call_deferred(SNAME("grab_focus" )); |
1277 | } break; |
1278 | case EDIT_MOVE_LINE_UP: { |
1279 | code_editor->move_lines_up(); |
1280 | } break; |
1281 | case EDIT_MOVE_LINE_DOWN: { |
1282 | code_editor->move_lines_down(); |
1283 | } break; |
1284 | case EDIT_INDENT: { |
1285 | Ref<Script> scr = script; |
1286 | if (scr.is_null()) { |
1287 | return; |
1288 | } |
1289 | tx->indent_lines(); |
1290 | } break; |
1291 | case EDIT_UNINDENT: { |
1292 | Ref<Script> scr = script; |
1293 | if (scr.is_null()) { |
1294 | return; |
1295 | } |
1296 | tx->unindent_lines(); |
1297 | } break; |
1298 | case EDIT_DELETE_LINE: { |
1299 | code_editor->delete_lines(); |
1300 | } break; |
1301 | case EDIT_DUPLICATE_SELECTION: { |
1302 | code_editor->duplicate_selection(); |
1303 | } break; |
1304 | case EDIT_TOGGLE_FOLD_LINE: { |
1305 | int previous_line = -1; |
1306 | for (int caret_idx : tx->get_caret_index_edit_order()) { |
1307 | int line_idx = tx->get_caret_line(caret_idx); |
1308 | if (line_idx != previous_line) { |
1309 | tx->toggle_foldable_line(line_idx); |
1310 | previous_line = line_idx; |
1311 | } |
1312 | } |
1313 | tx->queue_redraw(); |
1314 | } break; |
1315 | case EDIT_FOLD_ALL_LINES: { |
1316 | tx->fold_all_lines(); |
1317 | tx->queue_redraw(); |
1318 | } break; |
1319 | case EDIT_UNFOLD_ALL_LINES: { |
1320 | tx->unfold_all_lines(); |
1321 | tx->queue_redraw(); |
1322 | } break; |
1323 | case EDIT_CREATE_CODE_REGION: { |
1324 | tx->create_code_region(); |
1325 | } break; |
1326 | case EDIT_TOGGLE_COMMENT: { |
1327 | _edit_option_toggle_inline_comment(); |
1328 | } break; |
1329 | case EDIT_COMPLETE: { |
1330 | tx->request_code_completion(true); |
1331 | } break; |
1332 | case EDIT_AUTO_INDENT: { |
1333 | String text = tx->get_text(); |
1334 | Ref<Script> scr = script; |
1335 | if (scr.is_null()) { |
1336 | return; |
1337 | } |
1338 | |
1339 | tx->begin_complex_operation(); |
1340 | int begin, end; |
1341 | if (tx->has_selection()) { |
1342 | begin = tx->get_selection_from_line(); |
1343 | end = tx->get_selection_to_line(); |
1344 | // ignore if the cursor is not past the first column |
1345 | if (tx->get_selection_to_column() == 0) { |
1346 | end--; |
1347 | } |
1348 | } else { |
1349 | begin = 0; |
1350 | end = tx->get_line_count() - 1; |
1351 | } |
1352 | scr->get_language()->auto_indent_code(text, begin, end); |
1353 | Vector<String> lines = text.split("\n" ); |
1354 | for (int i = begin; i <= end; ++i) { |
1355 | tx->set_line(i, lines[i]); |
1356 | } |
1357 | |
1358 | tx->end_complex_operation(); |
1359 | } break; |
1360 | case EDIT_TRIM_TRAILING_WHITESAPCE: { |
1361 | trim_trailing_whitespace(); |
1362 | } break; |
1363 | case EDIT_CONVERT_INDENT_TO_SPACES: { |
1364 | tx->set_indent_using_spaces(true); |
1365 | convert_indent(); |
1366 | } break; |
1367 | case EDIT_CONVERT_INDENT_TO_TABS: { |
1368 | tx->set_indent_using_spaces(false); |
1369 | convert_indent(); |
1370 | } break; |
1371 | case EDIT_PICK_COLOR: { |
1372 | color_panel->popup(); |
1373 | } break; |
1374 | case EDIT_TO_UPPERCASE: { |
1375 | _convert_case(CodeTextEditor::UPPER); |
1376 | } break; |
1377 | case EDIT_TO_LOWERCASE: { |
1378 | _convert_case(CodeTextEditor::LOWER); |
1379 | } break; |
1380 | case EDIT_CAPITALIZE: { |
1381 | _convert_case(CodeTextEditor::CAPITALIZE); |
1382 | } break; |
1383 | case EDIT_EVALUATE: { |
1384 | Expression expression; |
1385 | tx->begin_complex_operation(); |
1386 | for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { |
1387 | Vector<String> lines = tx->get_selected_text(caret_idx).split("\n" ); |
1388 | PackedStringArray results; |
1389 | |
1390 | for (int i = 0; i < lines.size(); i++) { |
1391 | String line = lines[i]; |
1392 | String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); // Extract the whitespace at the beginning. |
1393 | if (expression.parse(line) == OK) { |
1394 | Variant result = expression.execute(Array(), Variant(), false, true); |
1395 | if (expression.get_error_text().is_empty()) { |
1396 | results.push_back(whitespace + result.get_construct_string()); |
1397 | } else { |
1398 | results.push_back(line); |
1399 | } |
1400 | } else { |
1401 | results.push_back(line); |
1402 | } |
1403 | } |
1404 | tx->insert_text_at_caret(String("\n" ).join(results), caret_idx); |
1405 | } |
1406 | tx->end_complex_operation(); |
1407 | } break; |
1408 | case EDIT_TOGGLE_WORD_WRAP: { |
1409 | TextEdit::LineWrappingMode wrap = code_editor->get_text_editor()->get_line_wrapping_mode(); |
1410 | code_editor->get_text_editor()->set_line_wrapping_mode(wrap == TextEdit::LINE_WRAPPING_BOUNDARY ? TextEdit::LINE_WRAPPING_NONE : TextEdit::LINE_WRAPPING_BOUNDARY); |
1411 | } break; |
1412 | case SEARCH_FIND: { |
1413 | code_editor->get_find_replace_bar()->popup_search(); |
1414 | } break; |
1415 | case SEARCH_FIND_NEXT: { |
1416 | code_editor->get_find_replace_bar()->search_next(); |
1417 | } break; |
1418 | case SEARCH_FIND_PREV: { |
1419 | code_editor->get_find_replace_bar()->search_prev(); |
1420 | } break; |
1421 | case SEARCH_REPLACE: { |
1422 | code_editor->get_find_replace_bar()->popup_replace(); |
1423 | } break; |
1424 | case SEARCH_IN_FILES: { |
1425 | String selected_text = tx->get_selected_text(); |
1426 | |
1427 | // Yep, because it doesn't make sense to instance this dialog for every single script open... |
1428 | // So this will be delegated to the ScriptEditor. |
1429 | emit_signal(SNAME("search_in_files_requested" ), selected_text); |
1430 | } break; |
1431 | case REPLACE_IN_FILES: { |
1432 | String selected_text = tx->get_selected_text(); |
1433 | |
1434 | emit_signal(SNAME("replace_in_files_requested" ), selected_text); |
1435 | } break; |
1436 | case SEARCH_LOCATE_FUNCTION: { |
1437 | quick_open->popup_dialog(get_functions()); |
1438 | quick_open->set_title(TTR("Go to Function" )); |
1439 | } break; |
1440 | case SEARCH_GOTO_LINE: { |
1441 | goto_line_dialog->popup_find_line(tx); |
1442 | } break; |
1443 | case BOOKMARK_TOGGLE: { |
1444 | code_editor->toggle_bookmark(); |
1445 | } break; |
1446 | case BOOKMARK_GOTO_NEXT: { |
1447 | code_editor->goto_next_bookmark(); |
1448 | } break; |
1449 | case BOOKMARK_GOTO_PREV: { |
1450 | code_editor->goto_prev_bookmark(); |
1451 | } break; |
1452 | case BOOKMARK_REMOVE_ALL: { |
1453 | code_editor->remove_all_bookmarks(); |
1454 | } break; |
1455 | case DEBUG_TOGGLE_BREAKPOINT: { |
1456 | Vector<int> caret_edit_order = tx->get_caret_index_edit_order(); |
1457 | caret_edit_order.reverse(); |
1458 | int last_line = -1; |
1459 | for (const int &c : caret_edit_order) { |
1460 | int from = tx->has_selection(c) ? tx->get_selection_from_line(c) : tx->get_caret_line(c); |
1461 | from += from == last_line ? 1 : 0; |
1462 | int to = tx->has_selection(c) ? tx->get_selection_to_line(c) : tx->get_caret_line(c); |
1463 | if (to < from) { |
1464 | continue; |
1465 | } |
1466 | // Check first if there's any lines with breakpoints in the selection. |
1467 | bool selection_has_breakpoints = false; |
1468 | for (int line = from; line <= to; line++) { |
1469 | if (tx->is_line_breakpointed(line)) { |
1470 | selection_has_breakpoints = true; |
1471 | break; |
1472 | } |
1473 | } |
1474 | |
1475 | // Set breakpoint on caret or remove all bookmarks from the selection. |
1476 | if (!selection_has_breakpoints) { |
1477 | if (tx->get_caret_line(c) != last_line) { |
1478 | tx->set_line_as_breakpoint(tx->get_caret_line(c), true); |
1479 | } |
1480 | } else { |
1481 | for (int line = from; line <= to; line++) { |
1482 | tx->set_line_as_breakpoint(line, false); |
1483 | } |
1484 | } |
1485 | last_line = to; |
1486 | } |
1487 | } break; |
1488 | case DEBUG_REMOVE_ALL_BREAKPOINTS: { |
1489 | PackedInt32Array bpoints = tx->get_breakpointed_lines(); |
1490 | |
1491 | for (int i = 0; i < bpoints.size(); i++) { |
1492 | int line = bpoints[i]; |
1493 | bool dobreak = !tx->is_line_breakpointed(line); |
1494 | tx->set_line_as_breakpoint(line, dobreak); |
1495 | EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); |
1496 | } |
1497 | } break; |
1498 | case DEBUG_GOTO_NEXT_BREAKPOINT: { |
1499 | PackedInt32Array bpoints = tx->get_breakpointed_lines(); |
1500 | if (bpoints.size() <= 0) { |
1501 | return; |
1502 | } |
1503 | |
1504 | int current_line = tx->get_caret_line(); |
1505 | int bpoint_idx = 0; |
1506 | if (current_line < (int)bpoints[bpoints.size() - 1]) { |
1507 | while (bpoint_idx < bpoints.size() && bpoints[bpoint_idx] <= current_line) { |
1508 | bpoint_idx++; |
1509 | } |
1510 | } |
1511 | code_editor->goto_line_centered(bpoints[bpoint_idx]); |
1512 | } break; |
1513 | case DEBUG_GOTO_PREV_BREAKPOINT: { |
1514 | PackedInt32Array bpoints = tx->get_breakpointed_lines(); |
1515 | if (bpoints.size() <= 0) { |
1516 | return; |
1517 | } |
1518 | |
1519 | int current_line = tx->get_caret_line(); |
1520 | int bpoint_idx = bpoints.size() - 1; |
1521 | if (current_line > (int)bpoints[0]) { |
1522 | while (bpoint_idx >= 0 && bpoints[bpoint_idx] >= current_line) { |
1523 | bpoint_idx--; |
1524 | } |
1525 | } |
1526 | code_editor->goto_line_centered(bpoints[bpoint_idx]); |
1527 | } break; |
1528 | case HELP_CONTEXTUAL: { |
1529 | String text = tx->get_selected_text(0); |
1530 | if (text.is_empty()) { |
1531 | text = tx->get_word_under_caret(0); |
1532 | } |
1533 | if (!text.is_empty()) { |
1534 | emit_signal(SNAME("request_help" ), text); |
1535 | } |
1536 | } break; |
1537 | case LOOKUP_SYMBOL: { |
1538 | String text = tx->get_word_under_caret(0); |
1539 | if (text.is_empty()) { |
1540 | text = tx->get_selected_text(0); |
1541 | } |
1542 | if (!text.is_empty()) { |
1543 | _lookup_symbol(text, tx->get_caret_line(0), tx->get_caret_column(0)); |
1544 | } |
1545 | } break; |
1546 | } |
1547 | } |
1548 | |
1549 | void ScriptTextEditor::() { |
1550 | if (script.is_null()) { |
1551 | return; |
1552 | } |
1553 | |
1554 | String delimiter = "#" ; |
1555 | List<String> ; |
1556 | script->get_language()->get_comment_delimiters(&comment_delimiters); |
1557 | |
1558 | for (const String &script_delimiter : comment_delimiters) { |
1559 | if (!script_delimiter.contains(" " )) { |
1560 | delimiter = script_delimiter; |
1561 | break; |
1562 | } |
1563 | } |
1564 | |
1565 | code_editor->toggle_inline_comment(delimiter); |
1566 | } |
1567 | |
1568 | void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { |
1569 | ERR_FAIL_COND(p_highlighter.is_null()); |
1570 | |
1571 | highlighters[p_highlighter->_get_name()] = p_highlighter; |
1572 | highlighter_menu->add_radio_check_item(p_highlighter->_get_name()); |
1573 | } |
1574 | |
1575 | void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { |
1576 | ERR_FAIL_COND(p_highlighter.is_null()); |
1577 | |
1578 | HashMap<String, Ref<EditorSyntaxHighlighter>>::Iterator el = highlighters.begin(); |
1579 | while (el) { |
1580 | int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key); |
1581 | highlighter_menu->set_item_checked(highlighter_index, el->value == p_highlighter); |
1582 | ++el; |
1583 | } |
1584 | |
1585 | CodeEdit *te = code_editor->get_text_editor(); |
1586 | p_highlighter->_set_edited_resource(script); |
1587 | te->set_syntax_highlighter(p_highlighter); |
1588 | } |
1589 | |
1590 | void ScriptTextEditor::_change_syntax_highlighter(int p_idx) { |
1591 | set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]); |
1592 | } |
1593 | |
1594 | void ScriptTextEditor::_notification(int p_what) { |
1595 | switch (p_what) { |
1596 | case NOTIFICATION_THEME_CHANGED: |
1597 | if (!editor_enabled) { |
1598 | break; |
1599 | } |
1600 | if (is_visible_in_tree()) { |
1601 | _update_warnings(); |
1602 | _update_errors(); |
1603 | } |
1604 | [[fallthrough]]; |
1605 | case NOTIFICATION_ENTER_TREE: { |
1606 | code_editor->get_text_editor()->set_gutter_width(connection_gutter, code_editor->get_text_editor()->get_line_height()); |
1607 | } break; |
1608 | } |
1609 | } |
1610 | |
1611 | Control *ScriptTextEditor::() { |
1612 | return edit_hb; |
1613 | } |
1614 | |
1615 | void ScriptTextEditor::() { |
1616 | if (editor_enabled) { |
1617 | memdelete(edit_hb); |
1618 | } |
1619 | } |
1620 | |
1621 | void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { |
1622 | code_editor->set_find_replace_bar(p_bar); |
1623 | } |
1624 | |
1625 | void ScriptTextEditor::reload(bool p_soft) { |
1626 | CodeEdit *te = code_editor->get_text_editor(); |
1627 | Ref<Script> scr = script; |
1628 | if (scr.is_null()) { |
1629 | return; |
1630 | } |
1631 | scr->set_source_code(te->get_text()); |
1632 | bool soft = p_soft || scr->get_instance_base_type() == "EditorPlugin" ; // Always soft-reload editor plugins. |
1633 | |
1634 | scr->get_language()->reload_tool_script(scr, soft); |
1635 | } |
1636 | |
1637 | PackedInt32Array ScriptTextEditor::get_breakpoints() { |
1638 | return code_editor->get_text_editor()->get_breakpointed_lines(); |
1639 | } |
1640 | |
1641 | void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) { |
1642 | code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled); |
1643 | } |
1644 | |
1645 | void ScriptTextEditor::clear_breakpoints() { |
1646 | code_editor->get_text_editor()->clear_breakpointed_lines(); |
1647 | } |
1648 | |
1649 | void ScriptTextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { |
1650 | Variant args[1] = { this }; |
1651 | const Variant *argp[] = { &args[0] }; |
1652 | code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); |
1653 | } |
1654 | |
1655 | void ScriptTextEditor::set_debugger_active(bool p_active) { |
1656 | } |
1657 | |
1658 | Control *ScriptTextEditor::get_base_editor() const { |
1659 | return code_editor->get_text_editor(); |
1660 | } |
1661 | |
1662 | Variant ScriptTextEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { |
1663 | return Variant(); |
1664 | } |
1665 | |
1666 | bool ScriptTextEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { |
1667 | Dictionary d = p_data; |
1668 | if (d.has("type" ) && |
1669 | (String(d["type" ]) == "resource" || |
1670 | String(d["type" ]) == "files" || |
1671 | String(d["type" ]) == "nodes" || |
1672 | String(d["type" ]) == "obj_property" || |
1673 | String(d["type" ]) == "files_and_dirs" )) { |
1674 | return true; |
1675 | } |
1676 | |
1677 | return false; |
1678 | } |
1679 | |
1680 | static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref<Script> &script) { |
1681 | // Check scripts only for the nodes belonging to the edited scene. |
1682 | if (p_current_node == p_edited_scene || p_current_node->get_owner() == p_edited_scene) { |
1683 | Ref<Script> scr = p_current_node->get_script(); |
1684 | if (scr.is_valid() && scr == script) { |
1685 | return p_current_node; |
1686 | } |
1687 | } |
1688 | |
1689 | // Traverse all children, even the ones not owned by the edited scene as they |
1690 | // can still have child nodes added within the edited scene and thus owned by |
1691 | // it (e.g. nodes added to subscene's root or to its editable children). |
1692 | for (int i = 0; i < p_current_node->get_child_count(); i++) { |
1693 | Node *n = _find_script_node(p_edited_scene, p_current_node->get_child(i), script); |
1694 | if (n) { |
1695 | return n; |
1696 | } |
1697 | } |
1698 | |
1699 | return nullptr; |
1700 | } |
1701 | |
1702 | static String _quote_drop_data(const String &str) { |
1703 | // This function prepares a string for being "dropped" into the script editor. |
1704 | // The string can be a resource path, node path or property name. |
1705 | |
1706 | const bool using_single_quotes = EDITOR_GET("text_editor/completion/use_single_quotes" ); |
1707 | |
1708 | String escaped = str.c_escape(); |
1709 | |
1710 | // If string is double quoted, there is no need to escape single quotes. |
1711 | // We can revert the extra escaping added in c_escape(). |
1712 | if (!using_single_quotes) { |
1713 | escaped = escaped.replace("\\'" , "\'" ); |
1714 | } |
1715 | |
1716 | return escaped.quote(using_single_quotes ? "'" : "\"" ); |
1717 | } |
1718 | |
1719 | void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { |
1720 | Dictionary d = p_data; |
1721 | |
1722 | CodeEdit *te = code_editor->get_text_editor(); |
1723 | Point2i pos = te->get_line_column_at_pos(p_point); |
1724 | int row = pos.y; |
1725 | int col = pos.x; |
1726 | |
1727 | if (d.has("type" ) && String(d["type" ]) == "resource" ) { |
1728 | te->remove_secondary_carets(); |
1729 | Ref<Resource> res = d["resource" ]; |
1730 | if (!res.is_valid()) { |
1731 | return; |
1732 | } |
1733 | |
1734 | if (res->get_path().is_resource_file()) { |
1735 | EditorNode::get_singleton()->show_warning(TTR("Only resources from filesystem can be dropped." )); |
1736 | return; |
1737 | } |
1738 | |
1739 | te->set_caret_line(row); |
1740 | te->set_caret_column(col); |
1741 | te->insert_text_at_caret(res->get_path()); |
1742 | te->grab_focus(); |
1743 | } |
1744 | |
1745 | if (d.has("type" ) && (String(d["type" ]) == "files" || String(d["type" ]) == "files_and_dirs" )) { |
1746 | te->remove_secondary_carets(); |
1747 | Array files = d["files" ]; |
1748 | |
1749 | String text_to_drop; |
1750 | bool preload = Input::get_singleton()->is_key_pressed(Key::CTRL); |
1751 | for (int i = 0; i < files.size(); i++) { |
1752 | if (i > 0) { |
1753 | text_to_drop += ", " ; |
1754 | } |
1755 | |
1756 | if (preload) { |
1757 | text_to_drop += "preload(" + _quote_drop_data(String(files[i])) + ")" ; |
1758 | } else { |
1759 | text_to_drop += _quote_drop_data(String(files[i])); |
1760 | } |
1761 | } |
1762 | |
1763 | te->set_caret_line(row); |
1764 | te->set_caret_column(col); |
1765 | te->insert_text_at_caret(text_to_drop); |
1766 | te->grab_focus(); |
1767 | } |
1768 | |
1769 | if (d.has("type" ) && String(d["type" ]) == "nodes" ) { |
1770 | te->remove_secondary_carets(); |
1771 | Node *scene_root = get_tree()->get_edited_scene_root(); |
1772 | if (!scene_root) { |
1773 | EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene." )); |
1774 | return; |
1775 | } |
1776 | |
1777 | if (!ClassDB::is_parent_class(script->get_instance_base_type(), "Node" )) { |
1778 | EditorToaster::get_singleton()->popup_str(vformat(TTR("Can't drop nodes because script '%s' does not inherit Node." ), get_name()), EditorToaster::SEVERITY_WARNING); |
1779 | return; |
1780 | } |
1781 | |
1782 | Node *sn = _find_script_node(scene_root, scene_root, script); |
1783 | if (!sn) { |
1784 | sn = scene_root; |
1785 | } |
1786 | |
1787 | Array nodes = d["nodes" ]; |
1788 | String text_to_drop; |
1789 | |
1790 | if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { |
1791 | bool use_type = EDITOR_GET("text_editor/completion/add_type_hints" ); |
1792 | for (int i = 0; i < nodes.size(); i++) { |
1793 | NodePath np = nodes[i]; |
1794 | Node *node = get_node(np); |
1795 | if (!node) { |
1796 | continue; |
1797 | } |
1798 | |
1799 | bool is_unique = false; |
1800 | String path; |
1801 | if (node->is_unique_name_in_owner()) { |
1802 | path = node->get_name(); |
1803 | is_unique = true; |
1804 | } else { |
1805 | path = sn->get_path_to(node); |
1806 | } |
1807 | for (const String &segment : path.split("/" )) { |
1808 | if (!segment.is_valid_identifier()) { |
1809 | path = _quote_drop_data(path); |
1810 | break; |
1811 | } |
1812 | } |
1813 | |
1814 | String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); |
1815 | if (use_type) { |
1816 | StringName class_name = node->get_class_name(); |
1817 | Ref<Script> node_script = node->get_script(); |
1818 | if (node_script.is_valid()) { |
1819 | StringName global_node_script_name = node_script->get_global_name(); |
1820 | if (global_node_script_name != StringName()) { |
1821 | class_name = global_node_script_name; |
1822 | } |
1823 | } |
1824 | text_to_drop += vformat("@onready var %s: %s = %c%s\n" , variable_name, class_name, is_unique ? '%' : '$', path); |
1825 | } else { |
1826 | text_to_drop += vformat("@onready var %s = %c%s\n" , variable_name, is_unique ? '%' : '$', path); |
1827 | } |
1828 | } |
1829 | } else { |
1830 | for (int i = 0; i < nodes.size(); i++) { |
1831 | if (i > 0) { |
1832 | text_to_drop += ", " ; |
1833 | } |
1834 | |
1835 | NodePath np = nodes[i]; |
1836 | Node *node = get_node(np); |
1837 | if (!node) { |
1838 | continue; |
1839 | } |
1840 | |
1841 | bool is_unique = false; |
1842 | String path; |
1843 | if (node->is_unique_name_in_owner()) { |
1844 | path = node->get_name(); |
1845 | is_unique = true; |
1846 | } else { |
1847 | path = sn->get_path_to(node); |
1848 | } |
1849 | |
1850 | for (const String &segment : path.split("/" )) { |
1851 | if (!segment.is_valid_identifier()) { |
1852 | path = _quote_drop_data(path); |
1853 | break; |
1854 | } |
1855 | } |
1856 | text_to_drop += (is_unique ? "%" : "$" ) + path; |
1857 | } |
1858 | } |
1859 | |
1860 | te->set_caret_line(row); |
1861 | te->set_caret_column(col); |
1862 | te->insert_text_at_caret(text_to_drop); |
1863 | te->grab_focus(); |
1864 | } |
1865 | |
1866 | if (d.has("type" ) && String(d["type" ]) == "obj_property" ) { |
1867 | te->remove_secondary_carets(); |
1868 | // It is unclear whether properties may contain single or double quotes. |
1869 | // Assume here that double-quotes may not exist. We are escaping single-quotes if necessary. |
1870 | const String text_to_drop = _quote_drop_data(String(d["property" ])); |
1871 | |
1872 | te->set_caret_line(row); |
1873 | te->set_caret_column(col); |
1874 | te->insert_text_at_caret(text_to_drop); |
1875 | te->grab_focus(); |
1876 | } |
1877 | } |
1878 | |
1879 | void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { |
1880 | Ref<InputEventMouseButton> mb = ev; |
1881 | Ref<InputEventKey> k = ev; |
1882 | Point2 local_pos; |
1883 | bool = false; |
1884 | |
1885 | CodeEdit *tx = code_editor->get_text_editor(); |
1886 | if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { |
1887 | local_pos = mb->get_global_position() - tx->get_global_position(); |
1888 | create_menu = true; |
1889 | } else if (k.is_valid() && k->is_action("ui_menu" , true)) { |
1890 | tx->adjust_viewport_to_caret(0); |
1891 | local_pos = tx->get_caret_draw_pos(0); |
1892 | create_menu = true; |
1893 | } |
1894 | |
1895 | if (create_menu) { |
1896 | Point2i pos = tx->get_line_column_at_pos(local_pos); |
1897 | int row = pos.y; |
1898 | int col = pos.x; |
1899 | |
1900 | tx->set_move_caret_on_right_click_enabled(EDITOR_GET("text_editor/behavior/navigation/move_caret_on_right_click" )); |
1901 | int caret_clicked = -1; |
1902 | if (tx->is_move_caret_on_right_click_enabled()) { |
1903 | if (tx->has_selection()) { |
1904 | for (int i = 0; i < tx->get_caret_count(); i++) { |
1905 | int from_line = tx->get_selection_from_line(i); |
1906 | int to_line = tx->get_selection_to_line(i); |
1907 | int from_column = tx->get_selection_from_column(i); |
1908 | int to_column = tx->get_selection_to_column(i); |
1909 | |
1910 | if (row >= from_line && row <= to_line && (row != from_line || col >= from_column) && (row != to_line || col <= to_column)) { |
1911 | // Right click in one of the selected text |
1912 | caret_clicked = i; |
1913 | break; |
1914 | } |
1915 | } |
1916 | } |
1917 | if (caret_clicked < 0) { |
1918 | tx->deselect(); |
1919 | tx->remove_secondary_carets(); |
1920 | caret_clicked = 0; |
1921 | tx->set_caret_line(row, false, false); |
1922 | tx->set_caret_column(col); |
1923 | } |
1924 | } |
1925 | |
1926 | String word_at_pos = tx->get_word_at_pos(local_pos); |
1927 | if (word_at_pos.is_empty()) { |
1928 | word_at_pos = tx->get_word_under_caret(caret_clicked); |
1929 | } |
1930 | if (word_at_pos.is_empty()) { |
1931 | word_at_pos = tx->get_selected_text(caret_clicked); |
1932 | } |
1933 | |
1934 | bool has_color = (word_at_pos == "Color" ); |
1935 | bool foldable = tx->can_fold_line(row) || tx->is_line_folded(row); |
1936 | bool open_docs = false; |
1937 | bool goto_definition = false; |
1938 | |
1939 | if (ScriptServer::is_global_class(word_at_pos) || word_at_pos.is_resource_file()) { |
1940 | open_docs = true; |
1941 | } else { |
1942 | Node *base = get_tree()->get_edited_scene_root(); |
1943 | if (base) { |
1944 | base = _find_node_for_script(base, base, script); |
1945 | } |
1946 | ScriptLanguage::LookupResult result; |
1947 | if (script->get_language()->lookup_code(tx->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { |
1948 | open_docs = true; |
1949 | } |
1950 | } |
1951 | |
1952 | if (has_color) { |
1953 | String line = tx->get_line(row); |
1954 | color_position.x = row; |
1955 | color_position.y = col; |
1956 | |
1957 | int begin = -1; |
1958 | int end = -1; |
1959 | enum EXPRESSION_PATTERNS { |
1960 | NOT_PARSED, |
1961 | RGBA_PARAMETER, // Color(float,float,float) or Color(float,float,float,float) |
1962 | COLOR_NAME, // Color.COLOR_NAME |
1963 | } expression_pattern = NOT_PARSED; |
1964 | |
1965 | for (int i = col; i < line.length(); i++) { |
1966 | if (line[i] == '(') { |
1967 | if (expression_pattern == NOT_PARSED) { |
1968 | begin = i; |
1969 | expression_pattern = RGBA_PARAMETER; |
1970 | } else { |
1971 | // Method call or '(' appearing twice. |
1972 | expression_pattern = NOT_PARSED; |
1973 | |
1974 | break; |
1975 | } |
1976 | } else if (expression_pattern == RGBA_PARAMETER && line[i] == ')' && end < 0) { |
1977 | end = i + 1; |
1978 | |
1979 | break; |
1980 | } else if (expression_pattern == NOT_PARSED && line[i] == '.') { |
1981 | begin = i; |
1982 | expression_pattern = COLOR_NAME; |
1983 | } else if (expression_pattern == COLOR_NAME && end < 0 && (line[i] == ' ' || line[i] == '\t')) { |
1984 | // Including '.' and spaces. |
1985 | continue; |
1986 | } else if (expression_pattern == COLOR_NAME && !(line[i] == '_' || ('A' <= line[i] && line[i] <= 'Z'))) { |
1987 | end = i; |
1988 | |
1989 | break; |
1990 | } |
1991 | } |
1992 | |
1993 | switch (expression_pattern) { |
1994 | case RGBA_PARAMETER: { |
1995 | color_args = line.substr(begin, end - begin); |
1996 | String stripped = color_args.replace(" " , "" ).replace("\t" , "" ).replace("(" , "" ).replace(")" , "" ); |
1997 | PackedFloat64Array color = stripped.split_floats("," ); |
1998 | if (color.size() > 2) { |
1999 | float alpha = color.size() > 3 ? color[3] : 1.0f; |
2000 | color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); |
2001 | } |
2002 | } break; |
2003 | case COLOR_NAME: { |
2004 | if (end < 0) { |
2005 | end = line.length(); |
2006 | } |
2007 | color_args = line.substr(begin, end - begin); |
2008 | const String color_name = color_args.replace(" " , "" ).replace("\t" , "" ).replace("." , "" ); |
2009 | const int color_index = Color::find_named_color(color_name); |
2010 | if (0 <= color_index) { |
2011 | const Color color_constant = Color::get_named_color(color_index); |
2012 | color_picker->set_pick_color(color_constant); |
2013 | } else { |
2014 | has_color = false; |
2015 | } |
2016 | } break; |
2017 | default: |
2018 | has_color = false; |
2019 | break; |
2020 | } |
2021 | if (has_color) { |
2022 | color_panel->set_position(get_screen_position() + local_pos); |
2023 | } |
2024 | } |
2025 | _make_context_menu(tx->has_selection(), has_color, foldable, open_docs, goto_definition, local_pos); |
2026 | } |
2027 | } |
2028 | |
2029 | void ScriptTextEditor::_color_changed(const Color &p_color) { |
2030 | String new_args; |
2031 | if (p_color.a == 1.0f) { |
2032 | new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ")" ); |
2033 | } else { |
2034 | new_args = String("(" + rtos(p_color.r) + ", " + rtos(p_color.g) + ", " + rtos(p_color.b) + ", " + rtos(p_color.a) + ")" ); |
2035 | } |
2036 | |
2037 | String line = code_editor->get_text_editor()->get_line(color_position.x); |
2038 | String line_with_replaced_args = line.replace(color_args, new_args); |
2039 | |
2040 | color_args = new_args; |
2041 | code_editor->get_text_editor()->begin_complex_operation(); |
2042 | code_editor->get_text_editor()->set_line(color_position.x, line_with_replaced_args); |
2043 | code_editor->get_text_editor()->end_complex_operation(); |
2044 | code_editor->get_text_editor()->queue_redraw(); |
2045 | } |
2046 | |
2047 | void ScriptTextEditor::() { |
2048 | const CodeEdit *tx = code_editor->get_text_editor(); |
2049 | PopupMenu * = edit_menu->get_popup(); |
2050 | popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); |
2051 | popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); |
2052 | } |
2053 | |
2054 | void ScriptTextEditor::(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) { |
2055 | context_menu->clear(); |
2056 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo" ), EDIT_UNDO); |
2057 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo" ), EDIT_REDO); |
2058 | |
2059 | context_menu->add_separator(); |
2060 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_cut" ), EDIT_CUT); |
2061 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_copy" ), EDIT_COPY); |
2062 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_paste" ), EDIT_PASTE); |
2063 | |
2064 | context_menu->add_separator(); |
2065 | context_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all" ), EDIT_SELECT_ALL); |
2066 | |
2067 | context_menu->add_separator(); |
2068 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent" ), EDIT_INDENT); |
2069 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent" ), EDIT_UNINDENT); |
2070 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment" ), EDIT_TOGGLE_COMMENT); |
2071 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark" ), BOOKMARK_TOGGLE); |
2072 | |
2073 | if (p_selection) { |
2074 | context_menu->add_separator(); |
2075 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_uppercase" ), EDIT_TO_UPPERCASE); |
2076 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_to_lowercase" ), EDIT_TO_LOWERCASE); |
2077 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection" ), EDIT_EVALUATE); |
2078 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/create_code_region" ), EDIT_CREATE_CODE_REGION); |
2079 | } |
2080 | if (p_foldable) { |
2081 | context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line" ), EDIT_TOGGLE_FOLD_LINE); |
2082 | } |
2083 | |
2084 | if (p_color || p_open_docs || p_goto_definition) { |
2085 | context_menu->add_separator(); |
2086 | if (p_open_docs) { |
2087 | context_menu->add_item(TTR("Lookup Symbol" ), LOOKUP_SYMBOL); |
2088 | } |
2089 | if (p_color) { |
2090 | context_menu->add_item(TTR("Pick Color" ), EDIT_PICK_COLOR); |
2091 | } |
2092 | } |
2093 | |
2094 | const CodeEdit *tx = code_editor->get_text_editor(); |
2095 | context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); |
2096 | context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); |
2097 | |
2098 | context_menu->set_position(get_screen_position() + p_pos); |
2099 | context_menu->reset_size(); |
2100 | context_menu->popup(); |
2101 | } |
2102 | |
2103 | void ScriptTextEditor::_enable_code_editor() { |
2104 | ERR_FAIL_COND(code_editor->get_parent()); |
2105 | |
2106 | VSplitContainer *editor_box = memnew(VSplitContainer); |
2107 | add_child(editor_box); |
2108 | editor_box->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); |
2109 | editor_box->set_v_size_flags(SIZE_EXPAND_FILL); |
2110 | |
2111 | editor_box->add_child(code_editor); |
2112 | code_editor->connect("show_errors_panel" , callable_mp(this, &ScriptTextEditor::_show_errors_panel)); |
2113 | code_editor->connect("show_warnings_panel" , callable_mp(this, &ScriptTextEditor::_show_warnings_panel)); |
2114 | code_editor->connect("validate_script" , callable_mp(this, &ScriptTextEditor::_validate_script)); |
2115 | code_editor->connect("load_theme_settings" , callable_mp(this, &ScriptTextEditor::_load_theme_settings)); |
2116 | code_editor->get_text_editor()->connect("symbol_lookup" , callable_mp(this, &ScriptTextEditor::_lookup_symbol)); |
2117 | code_editor->get_text_editor()->connect("symbol_validate" , callable_mp(this, &ScriptTextEditor::_validate_symbol)); |
2118 | code_editor->get_text_editor()->connect("gutter_added" , callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); |
2119 | code_editor->get_text_editor()->connect("gutter_removed" , callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); |
2120 | code_editor->get_text_editor()->connect("gutter_clicked" , callable_mp(this, &ScriptTextEditor::_gutter_clicked)); |
2121 | code_editor->get_text_editor()->connect("gui_input" , callable_mp(this, &ScriptTextEditor::_text_edit_gui_input)); |
2122 | code_editor->show_toggle_scripts_button(); |
2123 | _update_gutter_indexes(); |
2124 | |
2125 | editor_box->add_child(warnings_panel); |
2126 | warnings_panel->add_theme_font_override( |
2127 | "normal_font" , EditorNode::get_singleton()->get_editor_theme()->get_font(SNAME("main" ), EditorStringName(EditorFonts))); |
2128 | warnings_panel->add_theme_font_size_override( |
2129 | "normal_font_size" , EditorNode::get_singleton()->get_editor_theme()->get_font_size(SNAME("main_size" ), EditorStringName(EditorFonts))); |
2130 | warnings_panel->connect("meta_clicked" , callable_mp(this, &ScriptTextEditor::_warning_clicked)); |
2131 | |
2132 | editor_box->add_child(errors_panel); |
2133 | errors_panel->add_theme_font_override( |
2134 | "normal_font" , EditorNode::get_singleton()->get_editor_theme()->get_font(SNAME("main" ), EditorStringName(EditorFonts))); |
2135 | errors_panel->add_theme_font_size_override( |
2136 | "normal_font_size" , EditorNode::get_singleton()->get_editor_theme()->get_font_size(SNAME("main_size" ), EditorStringName(EditorFonts))); |
2137 | errors_panel->connect("meta_clicked" , callable_mp(this, &ScriptTextEditor::_error_clicked)); |
2138 | |
2139 | add_child(context_menu); |
2140 | context_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2141 | |
2142 | add_child(color_panel); |
2143 | |
2144 | color_picker = memnew(ColorPicker); |
2145 | color_picker->set_deferred_mode(true); |
2146 | color_picker->connect("color_changed" , callable_mp(this, &ScriptTextEditor::_color_changed)); |
2147 | color_panel->connect("about_to_popup" , callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(color_picker)); |
2148 | |
2149 | color_panel->add_child(color_picker); |
2150 | |
2151 | quick_open = memnew(ScriptEditorQuickOpen); |
2152 | quick_open->connect("goto_line" , callable_mp(this, &ScriptTextEditor::_goto_line)); |
2153 | add_child(quick_open); |
2154 | |
2155 | goto_line_dialog = memnew(GotoLineDialog); |
2156 | add_child(goto_line_dialog); |
2157 | |
2158 | add_child(connection_info_dialog); |
2159 | |
2160 | edit_hb->add_child(edit_menu); |
2161 | edit_menu->connect("about_to_popup" , callable_mp(this, &ScriptTextEditor::_prepare_edit_menu)); |
2162 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo" ), EDIT_UNDO); |
2163 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo" ), EDIT_REDO); |
2164 | edit_menu->get_popup()->add_separator(); |
2165 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut" ), EDIT_CUT); |
2166 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy" ), EDIT_COPY); |
2167 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste" ), EDIT_PASTE); |
2168 | edit_menu->get_popup()->add_separator(); |
2169 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_select_all" ), EDIT_SELECT_ALL); |
2170 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection" ), EDIT_DUPLICATE_SELECTION); |
2171 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection" ), EDIT_EVALUATE); |
2172 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_word_wrap" ), EDIT_TOGGLE_WORD_WRAP); |
2173 | edit_menu->get_popup()->add_separator(); |
2174 | { |
2175 | PopupMenu * = memnew(PopupMenu); |
2176 | sub_menu->set_name("line_menu" ); |
2177 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up" ), EDIT_MOVE_LINE_UP); |
2178 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down" ), EDIT_MOVE_LINE_DOWN); |
2179 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent" ), EDIT_INDENT); |
2180 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unindent" ), EDIT_UNINDENT); |
2181 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line" ), EDIT_DELETE_LINE); |
2182 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment" ), EDIT_TOGGLE_COMMENT); |
2183 | sub_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2184 | edit_menu->get_popup()->add_child(sub_menu); |
2185 | edit_menu->get_popup()->add_submenu_item(TTR("Line" ), "line_menu" ); |
2186 | } |
2187 | { |
2188 | PopupMenu * = memnew(PopupMenu); |
2189 | sub_menu->set_name("folding_menu" ); |
2190 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line" ), EDIT_TOGGLE_FOLD_LINE); |
2191 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines" ), EDIT_FOLD_ALL_LINES); |
2192 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines" ), EDIT_UNFOLD_ALL_LINES); |
2193 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/create_code_region" ), EDIT_CREATE_CODE_REGION); |
2194 | sub_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2195 | edit_menu->get_popup()->add_child(sub_menu); |
2196 | edit_menu->get_popup()->add_submenu_item(TTR("Folding" ), "folding_menu" ); |
2197 | } |
2198 | edit_menu->get_popup()->add_separator(); |
2199 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query" ), EDIT_COMPLETE); |
2200 | edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace" ), EDIT_TRIM_TRAILING_WHITESAPCE); |
2201 | { |
2202 | PopupMenu * = memnew(PopupMenu); |
2203 | sub_menu->set_name("indent_menu" ); |
2204 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces" ), EDIT_CONVERT_INDENT_TO_SPACES); |
2205 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs" ), EDIT_CONVERT_INDENT_TO_TABS); |
2206 | sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent" ), EDIT_AUTO_INDENT); |
2207 | sub_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2208 | edit_menu->get_popup()->add_child(sub_menu); |
2209 | edit_menu->get_popup()->add_submenu_item(TTR("Indentation" ), "indent_menu" ); |
2210 | } |
2211 | edit_menu->get_popup()->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2212 | edit_menu->get_popup()->add_separator(); |
2213 | { |
2214 | PopupMenu * = memnew(PopupMenu); |
2215 | sub_menu->set_name("convert_case" ); |
2216 | sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase" , TTR("Uppercase" ), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); |
2217 | sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase" , TTR("Lowercase" ), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); |
2218 | sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize" , TTR("Capitalize" ), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); |
2219 | sub_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2220 | edit_menu->get_popup()->add_child(sub_menu); |
2221 | edit_menu->get_popup()->add_submenu_item(TTR("Convert Case" ), "convert_case" ); |
2222 | } |
2223 | edit_menu->get_popup()->add_child(highlighter_menu); |
2224 | edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter" ), "highlighter_menu" ); |
2225 | highlighter_menu->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter)); |
2226 | |
2227 | edit_hb->add_child(search_menu); |
2228 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find" ), SEARCH_FIND); |
2229 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next" ), SEARCH_FIND_NEXT); |
2230 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous" ), SEARCH_FIND_PREV); |
2231 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace" ), SEARCH_REPLACE); |
2232 | search_menu->get_popup()->add_separator(); |
2233 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files" ), SEARCH_IN_FILES); |
2234 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files" ), REPLACE_IN_FILES); |
2235 | search_menu->get_popup()->add_separator(); |
2236 | search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help" ), HELP_CONTEXTUAL); |
2237 | search_menu->get_popup()->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2238 | |
2239 | _load_theme_settings(); |
2240 | |
2241 | edit_hb->add_child(goto_menu); |
2242 | goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function" ), SEARCH_LOCATE_FUNCTION); |
2243 | goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line" ), SEARCH_GOTO_LINE); |
2244 | goto_menu->get_popup()->add_separator(); |
2245 | |
2246 | goto_menu->get_popup()->add_child(bookmarks_menu); |
2247 | goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks" ), "Bookmarks" ); |
2248 | _update_bookmark_list(); |
2249 | bookmarks_menu->connect("about_to_popup" , callable_mp(this, &ScriptTextEditor::_update_bookmark_list)); |
2250 | bookmarks_menu->connect("index_pressed" , callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed)); |
2251 | |
2252 | goto_menu->get_popup()->add_child(breakpoints_menu); |
2253 | goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints" ), "Breakpoints" ); |
2254 | _update_breakpoint_list(); |
2255 | breakpoints_menu->connect("about_to_popup" , callable_mp(this, &ScriptTextEditor::_update_breakpoint_list)); |
2256 | breakpoints_menu->connect("index_pressed" , callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed)); |
2257 | |
2258 | goto_menu->get_popup()->connect("id_pressed" , callable_mp(this, &ScriptTextEditor::_edit_option)); |
2259 | } |
2260 | |
2261 | ScriptTextEditor::ScriptTextEditor() { |
2262 | code_editor = memnew(CodeTextEditor); |
2263 | code_editor->add_theme_constant_override("separation" , 2); |
2264 | code_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); |
2265 | code_editor->set_code_complete_func(_code_complete_scripts, this); |
2266 | code_editor->set_v_size_flags(SIZE_EXPAND_FILL); |
2267 | |
2268 | code_editor->get_text_editor()->set_draw_breakpoints_gutter(true); |
2269 | code_editor->get_text_editor()->set_draw_executing_lines_gutter(true); |
2270 | code_editor->get_text_editor()->connect("breakpoint_toggled" , callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); |
2271 | |
2272 | connection_gutter = 1; |
2273 | code_editor->get_text_editor()->add_gutter(connection_gutter); |
2274 | code_editor->get_text_editor()->set_gutter_name(connection_gutter, "connection_gutter" ); |
2275 | code_editor->get_text_editor()->set_gutter_draw(connection_gutter, false); |
2276 | code_editor->get_text_editor()->set_gutter_overwritable(connection_gutter, true); |
2277 | code_editor->get_text_editor()->set_gutter_type(connection_gutter, TextEdit::GUTTER_TYPE_ICON); |
2278 | |
2279 | warnings_panel = memnew(RichTextLabel); |
2280 | warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); |
2281 | warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); |
2282 | warnings_panel->set_meta_underline(true); |
2283 | warnings_panel->set_selection_enabled(true); |
2284 | warnings_panel->set_context_menu_enabled(true); |
2285 | warnings_panel->set_focus_mode(FOCUS_CLICK); |
2286 | warnings_panel->hide(); |
2287 | |
2288 | errors_panel = memnew(RichTextLabel); |
2289 | errors_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); |
2290 | errors_panel->set_h_size_flags(SIZE_EXPAND_FILL); |
2291 | errors_panel->set_meta_underline(true); |
2292 | errors_panel->set_selection_enabled(true); |
2293 | errors_panel->set_context_menu_enabled(true); |
2294 | errors_panel->set_focus_mode(FOCUS_CLICK); |
2295 | errors_panel->hide(); |
2296 | |
2297 | update_settings(); |
2298 | |
2299 | code_editor->get_text_editor()->set_code_hint_draw_below(EDITOR_GET("text_editor/completion/put_callhint_tooltip_below_current_line" )); |
2300 | |
2301 | code_editor->get_text_editor()->set_symbol_lookup_on_click_enabled(true); |
2302 | code_editor->get_text_editor()->set_context_menu_enabled(false); |
2303 | |
2304 | context_menu = memnew(PopupMenu); |
2305 | |
2306 | color_panel = memnew(PopupPanel); |
2307 | |
2308 | edit_hb = memnew(HBoxContainer); |
2309 | |
2310 | edit_menu = memnew(MenuButton); |
2311 | edit_menu->set_text(TTR("Edit" )); |
2312 | edit_menu->set_switch_on_hover(true); |
2313 | edit_menu->set_shortcut_context(this); |
2314 | |
2315 | highlighter_menu = memnew(PopupMenu); |
2316 | highlighter_menu->set_name("highlighter_menu" ); |
2317 | |
2318 | Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; |
2319 | plain_highlighter.instantiate(); |
2320 | add_syntax_highlighter(plain_highlighter); |
2321 | |
2322 | Ref<EditorStandardSyntaxHighlighter> highlighter; |
2323 | highlighter.instantiate(); |
2324 | add_syntax_highlighter(highlighter); |
2325 | set_syntax_highlighter(highlighter); |
2326 | |
2327 | search_menu = memnew(MenuButton); |
2328 | search_menu->set_text(TTR("Search" )); |
2329 | search_menu->set_switch_on_hover(true); |
2330 | search_menu->set_shortcut_context(this); |
2331 | |
2332 | goto_menu = memnew(MenuButton); |
2333 | goto_menu->set_text(TTR("Go To" )); |
2334 | goto_menu->set_switch_on_hover(true); |
2335 | goto_menu->set_shortcut_context(this); |
2336 | |
2337 | bookmarks_menu = memnew(PopupMenu); |
2338 | bookmarks_menu->set_name("Bookmarks" ); |
2339 | |
2340 | breakpoints_menu = memnew(PopupMenu); |
2341 | breakpoints_menu->set_name("Breakpoints" ); |
2342 | |
2343 | connection_info_dialog = memnew(ConnectionInfoDialog); |
2344 | |
2345 | SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor); |
2346 | } |
2347 | |
2348 | ScriptTextEditor::~ScriptTextEditor() { |
2349 | highlighters.clear(); |
2350 | |
2351 | if (!editor_enabled) { |
2352 | memdelete(code_editor); |
2353 | memdelete(warnings_panel); |
2354 | memdelete(errors_panel); |
2355 | memdelete(context_menu); |
2356 | memdelete(color_panel); |
2357 | memdelete(edit_hb); |
2358 | memdelete(edit_menu); |
2359 | memdelete(highlighter_menu); |
2360 | memdelete(search_menu); |
2361 | memdelete(goto_menu); |
2362 | memdelete(bookmarks_menu); |
2363 | memdelete(breakpoints_menu); |
2364 | memdelete(connection_info_dialog); |
2365 | } |
2366 | } |
2367 | |
2368 | static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) { |
2369 | if (Object::cast_to<Script>(*p_resource)) { |
2370 | return memnew(ScriptTextEditor); |
2371 | } |
2372 | return nullptr; |
2373 | } |
2374 | |
2375 | void ScriptTextEditor::register_editor() { |
2376 | ED_SHORTCUT("script_text_editor/move_up" , TTR("Move Up" ), KeyModifierMask::ALT | Key::UP); |
2377 | ED_SHORTCUT("script_text_editor/move_down" , TTR("Move Down" ), KeyModifierMask::ALT | Key::DOWN); |
2378 | ED_SHORTCUT("script_text_editor/delete_line" , TTR("Delete Line" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::K); |
2379 | |
2380 | // Leave these at zero, same can be accomplished with tab/shift-tab, including selection. |
2381 | // The next/previous in history shortcut in this case makes a lot more sense. |
2382 | |
2383 | ED_SHORTCUT("script_text_editor/indent" , TTR("Indent" ), Key::NONE); |
2384 | ED_SHORTCUT("script_text_editor/unindent" , TTR("Unindent" ), KeyModifierMask::SHIFT | Key::TAB); |
2385 | ED_SHORTCUT_ARRAY("script_text_editor/toggle_comment" , TTR("Toggle Comment" ), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::K), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::SLASH) }); |
2386 | ED_SHORTCUT("script_text_editor/toggle_fold_line" , TTR("Fold/Unfold Line" ), KeyModifierMask::ALT | Key::F); |
2387 | ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_fold_line" , "macos" , KeyModifierMask::CTRL | KeyModifierMask::META | Key::F); |
2388 | ED_SHORTCUT("script_text_editor/fold_all_lines" , TTR("Fold All Lines" ), Key::NONE); |
2389 | ED_SHORTCUT("script_text_editor/create_code_region" , TTR("Create Code Region" ), KeyModifierMask::ALT | Key::R); |
2390 | ED_SHORTCUT("script_text_editor/unfold_all_lines" , TTR("Unfold All Lines" ), Key::NONE); |
2391 | ED_SHORTCUT("script_text_editor/duplicate_selection" , TTR("Duplicate Selection" ), KeyModifierMask::SHIFT | KeyModifierMask::CTRL | Key::D); |
2392 | ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection" , "macos" , KeyModifierMask::SHIFT | KeyModifierMask::META | Key::C); |
2393 | ED_SHORTCUT("script_text_editor/evaluate_selection" , TTR("Evaluate Selection" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::E); |
2394 | ED_SHORTCUT("script_text_editor/toggle_word_wrap" , TTR("Toggle Word Wrap" ), KeyModifierMask::ALT | Key::Z); |
2395 | ED_SHORTCUT("script_text_editor/trim_trailing_whitespace" , TTR("Trim Trailing Whitespace" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::T); |
2396 | ED_SHORTCUT("script_text_editor/convert_indent_to_spaces" , TTR("Convert Indent to Spaces" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::Y); |
2397 | ED_SHORTCUT("script_text_editor/convert_indent_to_tabs" , TTR("Convert Indent to Tabs" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::I); |
2398 | ED_SHORTCUT("script_text_editor/auto_indent" , TTR("Auto Indent" ), KeyModifierMask::CMD_OR_CTRL | Key::I); |
2399 | |
2400 | ED_SHORTCUT_AND_COMMAND("script_text_editor/find" , TTR("Find..." ), KeyModifierMask::CMD_OR_CTRL | Key::F); |
2401 | |
2402 | ED_SHORTCUT("script_text_editor/find_next" , TTR("Find Next" ), Key::F3); |
2403 | ED_SHORTCUT_OVERRIDE("script_text_editor/find_next" , "macos" , KeyModifierMask::META | Key::G); |
2404 | |
2405 | ED_SHORTCUT("script_text_editor/find_previous" , TTR("Find Previous" ), KeyModifierMask::SHIFT | Key::F3); |
2406 | ED_SHORTCUT_OVERRIDE("script_text_editor/find_previous" , "macos" , KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G); |
2407 | |
2408 | ED_SHORTCUT_AND_COMMAND("script_text_editor/replace" , TTR("Replace..." ), KeyModifierMask::CTRL | Key::R); |
2409 | ED_SHORTCUT_OVERRIDE("script_text_editor/replace" , "macos" , KeyModifierMask::ALT | KeyModifierMask::META | Key::F); |
2410 | |
2411 | ED_SHORTCUT("script_text_editor/find_in_files" , TTR("Find in Files..." ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F); |
2412 | ED_SHORTCUT("script_text_editor/replace_in_files" , TTR("Replace in Files..." ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::R); |
2413 | |
2414 | ED_SHORTCUT("script_text_editor/contextual_help" , TTR("Contextual Help" ), KeyModifierMask::ALT | Key::F1); |
2415 | ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help" , "macos" , KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE); |
2416 | |
2417 | ED_SHORTCUT("script_text_editor/toggle_bookmark" , TTR("Toggle Bookmark" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::B); |
2418 | ED_SHORTCUT("script_text_editor/goto_next_bookmark" , TTR("Go to Next Bookmark" ), KeyModifierMask::CMD_OR_CTRL | Key::B); |
2419 | ED_SHORTCUT("script_text_editor/goto_previous_bookmark" , TTR("Go to Previous Bookmark" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::B); |
2420 | ED_SHORTCUT("script_text_editor/remove_all_bookmarks" , TTR("Remove All Bookmarks" ), Key::NONE); |
2421 | |
2422 | ED_SHORTCUT("script_text_editor/goto_function" , TTR("Go to Function..." ), KeyModifierMask::ALT | KeyModifierMask::CTRL | Key::F); |
2423 | ED_SHORTCUT_OVERRIDE("script_text_editor/goto_function" , "macos" , KeyModifierMask::CTRL | KeyModifierMask::META | Key::J); |
2424 | |
2425 | ED_SHORTCUT("script_text_editor/goto_line" , TTR("Go to Line..." ), KeyModifierMask::CMD_OR_CTRL | Key::L); |
2426 | |
2427 | ED_SHORTCUT("script_text_editor/toggle_breakpoint" , TTR("Toggle Breakpoint" ), Key::F9); |
2428 | ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint" , "macos" , KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B); |
2429 | |
2430 | ED_SHORTCUT("script_text_editor/remove_all_breakpoints" , TTR("Remove All Breakpoints" ), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9); |
2431 | ED_SHORTCUT("script_text_editor/goto_next_breakpoint" , TTR("Go to Next Breakpoint" ), KeyModifierMask::CMD_OR_CTRL | Key::PERIOD); |
2432 | ED_SHORTCUT("script_text_editor/goto_previous_breakpoint" , TTR("Go to Previous Breakpoint" ), KeyModifierMask::CMD_OR_CTRL | Key::COMMA); |
2433 | |
2434 | ScriptEditor::register_create_script_editor_function(create_editor); |
2435 | } |
2436 | |
2437 | void ScriptTextEditor::validate() { |
2438 | this->code_editor->validate_script(); |
2439 | } |
2440 | |