1 | /**************************************************************************/ |
2 | /* editor_debugger_node.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 "editor_debugger_node.h" |
32 | |
33 | #include "core/object/undo_redo.h" |
34 | #include "editor/debugger/editor_debugger_tree.h" |
35 | #include "editor/debugger/script_editor_debugger.h" |
36 | #include "editor/editor_log.h" |
37 | #include "editor/editor_node.h" |
38 | #include "editor/editor_settings.h" |
39 | #include "editor/editor_string_names.h" |
40 | #include "editor/editor_undo_redo_manager.h" |
41 | #include "editor/gui/editor_run_bar.h" |
42 | #include "editor/inspector_dock.h" |
43 | #include "editor/plugins/editor_debugger_plugin.h" |
44 | #include "editor/plugins/script_editor_plugin.h" |
45 | #include "editor/scene_tree_dock.h" |
46 | #include "scene/gui/menu_button.h" |
47 | #include "scene/gui/tab_container.h" |
48 | #include "scene/resources/packed_scene.h" |
49 | |
50 | template <typename Func> |
51 | void _for_all(TabContainer *p_node, const Func &p_func) { |
52 | for (int i = 0; i < p_node->get_tab_count(); i++) { |
53 | ScriptEditorDebugger *dbg = Object::cast_to<ScriptEditorDebugger>(p_node->get_tab_control(i)); |
54 | ERR_FAIL_NULL(dbg); |
55 | p_func(dbg); |
56 | } |
57 | } |
58 | |
59 | EditorDebuggerNode *EditorDebuggerNode::singleton = nullptr; |
60 | |
61 | EditorDebuggerNode::EditorDebuggerNode() { |
62 | if (!singleton) { |
63 | singleton = this; |
64 | } |
65 | |
66 | add_theme_constant_override("margin_left" , -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride" ), EditorStringName(EditorStyles))->get_margin(SIDE_LEFT)); |
67 | add_theme_constant_override("margin_right" , -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride" ), EditorStringName(EditorStyles))->get_margin(SIDE_RIGHT)); |
68 | |
69 | tabs = memnew(TabContainer); |
70 | tabs->set_tabs_visible(false); |
71 | tabs->connect("tab_changed" , callable_mp(this, &EditorDebuggerNode::_debugger_changed)); |
72 | add_child(tabs); |
73 | |
74 | Ref<StyleBoxEmpty> empty; |
75 | empty.instantiate(); |
76 | tabs->add_theme_style_override("panel" , empty); |
77 | |
78 | auto_switch_remote_scene_tree = EDITOR_GET("debugger/auto_switch_to_remote_scene_tree" ); |
79 | _add_debugger(); |
80 | |
81 | // Remote scene tree |
82 | remote_scene_tree = memnew(EditorDebuggerTree); |
83 | remote_scene_tree->connect("object_selected" , callable_mp(this, &EditorDebuggerNode::_remote_object_requested)); |
84 | remote_scene_tree->connect("save_node" , callable_mp(this, &EditorDebuggerNode::_save_node_requested)); |
85 | remote_scene_tree->connect("button_clicked" , callable_mp(this, &EditorDebuggerNode::_remote_tree_button_pressed)); |
86 | SceneTreeDock::get_singleton()->add_remote_tree_editor(remote_scene_tree); |
87 | SceneTreeDock::get_singleton()->connect("remote_tree_selected" , callable_mp(this, &EditorDebuggerNode::request_remote_tree)); |
88 | |
89 | remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval" ); |
90 | inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval" ); |
91 | |
92 | EditorRunBar::get_singleton()->get_pause_button()->connect("pressed" , callable_mp(this, &EditorDebuggerNode::_paused)); |
93 | } |
94 | |
95 | ScriptEditorDebugger *EditorDebuggerNode::_add_debugger() { |
96 | ScriptEditorDebugger *node = memnew(ScriptEditorDebugger); |
97 | |
98 | int id = tabs->get_tab_count(); |
99 | node->connect("stop_requested" , callable_mp(this, &EditorDebuggerNode::_debugger_wants_stop).bind(id)); |
100 | node->connect("stopped" , callable_mp(this, &EditorDebuggerNode::_debugger_stopped).bind(id)); |
101 | node->connect("stack_frame_selected" , callable_mp(this, &EditorDebuggerNode::_stack_frame_selected).bind(id)); |
102 | node->connect("error_selected" , callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id)); |
103 | node->connect("breakpoint_selected" , callable_mp(this, &EditorDebuggerNode::_error_selected).bind(id)); |
104 | node->connect("clear_execution" , callable_mp(this, &EditorDebuggerNode::_clear_execution)); |
105 | node->connect("breaked" , callable_mp(this, &EditorDebuggerNode::_breaked).bind(id)); |
106 | node->connect("remote_tree_updated" , callable_mp(this, &EditorDebuggerNode::_remote_tree_updated).bind(id)); |
107 | node->connect("remote_object_updated" , callable_mp(this, &EditorDebuggerNode::_remote_object_updated).bind(id)); |
108 | node->connect("remote_object_property_updated" , callable_mp(this, &EditorDebuggerNode::_remote_object_property_updated).bind(id)); |
109 | node->connect("remote_object_requested" , callable_mp(this, &EditorDebuggerNode::_remote_object_requested).bind(id)); |
110 | node->connect("errors_cleared" , callable_mp(this, &EditorDebuggerNode::_update_errors)); |
111 | |
112 | if (tabs->get_tab_count() > 0) { |
113 | get_debugger(0)->clear_style(); |
114 | } |
115 | |
116 | tabs->add_child(node); |
117 | |
118 | node->set_name("Session " + itos(tabs->get_tab_count())); |
119 | if (tabs->get_tab_count() > 1) { |
120 | node->clear_style(); |
121 | tabs->set_tabs_visible(true); |
122 | tabs->add_theme_style_override("panel" , EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("DebuggerPanel" ), EditorStringName(EditorStyles))); |
123 | } |
124 | |
125 | if (!debugger_plugins.is_empty()) { |
126 | for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) { |
127 | plugin->create_session(node); |
128 | } |
129 | } |
130 | |
131 | return node; |
132 | } |
133 | |
134 | void EditorDebuggerNode::_stack_frame_selected(int p_debugger) { |
135 | const ScriptEditorDebugger *dbg = get_debugger(p_debugger); |
136 | ERR_FAIL_NULL(dbg); |
137 | if (dbg != get_current_debugger()) { |
138 | return; |
139 | } |
140 | _text_editor_stack_goto(dbg); |
141 | } |
142 | |
143 | void EditorDebuggerNode::_error_selected(const String &p_file, int p_line, int p_debugger) { |
144 | Ref<Script> s = ResourceLoader::load(p_file); |
145 | emit_signal(SNAME("goto_script_line" ), s, p_line - 1); |
146 | } |
147 | |
148 | void EditorDebuggerNode::_text_editor_stack_goto(const ScriptEditorDebugger *p_debugger) { |
149 | String file = p_debugger->get_stack_script_file(); |
150 | if (file.is_empty()) { |
151 | return; |
152 | } |
153 | if (file.is_resource_file()) { |
154 | stack_script = ResourceLoader::load(file); |
155 | } else { |
156 | // If the script is built-in, it can be opened only if the scene is loaded in memory. |
157 | int i = file.find("::" ); |
158 | int j = file.rfind("(" , i); |
159 | if (j > -1) { // If the script is named, the string is "name (file)", so we need to extract the path. |
160 | file = file.substr(j + 1, file.find(")" , i) - j - 1); |
161 | } |
162 | Ref<PackedScene> ps = ResourceLoader::load(file.get_slice("::" , 0)); |
163 | stack_script = ResourceLoader::load(file); |
164 | } |
165 | const int line = p_debugger->get_stack_script_line() - 1; |
166 | emit_signal(SNAME("goto_script_line" ), stack_script, line); |
167 | emit_signal(SNAME("set_execution" ), stack_script, line); |
168 | stack_script.unref(); // Why?!? |
169 | } |
170 | |
171 | void EditorDebuggerNode::_bind_methods() { |
172 | // LiveDebug. |
173 | ClassDB::bind_method("live_debug_create_node" , &EditorDebuggerNode::live_debug_create_node); |
174 | ClassDB::bind_method("live_debug_instantiate_node" , &EditorDebuggerNode::live_debug_instantiate_node); |
175 | ClassDB::bind_method("live_debug_remove_node" , &EditorDebuggerNode::live_debug_remove_node); |
176 | ClassDB::bind_method("live_debug_remove_and_keep_node" , &EditorDebuggerNode::live_debug_remove_and_keep_node); |
177 | ClassDB::bind_method("live_debug_restore_node" , &EditorDebuggerNode::live_debug_restore_node); |
178 | ClassDB::bind_method("live_debug_duplicate_node" , &EditorDebuggerNode::live_debug_duplicate_node); |
179 | ClassDB::bind_method("live_debug_reparent_node" , &EditorDebuggerNode::live_debug_reparent_node); |
180 | |
181 | ADD_SIGNAL(MethodInfo("goto_script_line" )); |
182 | ADD_SIGNAL(MethodInfo("set_execution" , PropertyInfo("script" ), PropertyInfo(Variant::INT, "line" ))); |
183 | ADD_SIGNAL(MethodInfo("clear_execution" , PropertyInfo("script" ))); |
184 | ADD_SIGNAL(MethodInfo("breaked" , PropertyInfo(Variant::BOOL, "reallydid" ), PropertyInfo(Variant::BOOL, "can_debug" ))); |
185 | ADD_SIGNAL(MethodInfo("breakpoint_toggled" , PropertyInfo(Variant::STRING, "path" ), PropertyInfo(Variant::INT, "line" ), PropertyInfo(Variant::BOOL, "enabled" ))); |
186 | } |
187 | |
188 | void EditorDebuggerNode::register_undo_redo(UndoRedo *p_undo_redo) { |
189 | p_undo_redo->set_method_notify_callback(_method_changeds, this); |
190 | p_undo_redo->set_property_notify_callback(_property_changeds, this); |
191 | } |
192 | |
193 | EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() { |
194 | return Object::cast_to<EditorDebuggerRemoteObject>(ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_current())); |
195 | } |
196 | |
197 | ScriptEditorDebugger *EditorDebuggerNode::get_debugger(int p_id) const { |
198 | return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(p_id)); |
199 | } |
200 | |
201 | ScriptEditorDebugger *EditorDebuggerNode::get_current_debugger() const { |
202 | return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(tabs->get_current_tab())); |
203 | } |
204 | |
205 | ScriptEditorDebugger *EditorDebuggerNode::get_default_debugger() const { |
206 | return Object::cast_to<ScriptEditorDebugger>(tabs->get_tab_control(0)); |
207 | } |
208 | |
209 | String EditorDebuggerNode::get_server_uri() const { |
210 | ERR_FAIL_COND_V(server.is_null(), "" ); |
211 | return server->get_uri(); |
212 | } |
213 | |
214 | void EditorDebuggerNode::set_keep_open(bool p_keep_open) { |
215 | keep_open = p_keep_open; |
216 | if (keep_open) { |
217 | if (server.is_null() || !server->is_active()) { |
218 | start(); |
219 | } |
220 | } else { |
221 | bool found = false; |
222 | _for_all(tabs, [&](ScriptEditorDebugger *p_debugger) { |
223 | if (p_debugger->is_session_active()) { |
224 | found = true; |
225 | } |
226 | }); |
227 | if (!found) { |
228 | stop(); |
229 | } |
230 | } |
231 | } |
232 | |
233 | Error EditorDebuggerNode::start(const String &p_uri) { |
234 | ERR_FAIL_COND_V(p_uri.find("://" ) < 0, ERR_INVALID_PARAMETER); |
235 | if (keep_open && current_uri == p_uri && server.is_valid()) { |
236 | return OK; |
237 | } |
238 | stop(true); |
239 | current_uri = p_uri; |
240 | if (EDITOR_GET("run/output/always_open_output_on_play" )) { |
241 | EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorNode::get_log()); |
242 | } else { |
243 | EditorNode::get_singleton()->make_bottom_panel_item_visible(this); |
244 | } |
245 | server = Ref<EditorDebuggerServer>(EditorDebuggerServer::create(p_uri.substr(0, p_uri.find("://" ) + 3))); |
246 | const Error err = server->start(p_uri); |
247 | if (err != OK) { |
248 | return err; |
249 | } |
250 | set_process(true); |
251 | EditorNode::get_log()->add_message("--- Debugging process started ---" , EditorLog::MSG_TYPE_EDITOR); |
252 | return OK; |
253 | } |
254 | |
255 | void EditorDebuggerNode::stop(bool p_force) { |
256 | if (keep_open && !p_force) { |
257 | return; |
258 | } |
259 | current_uri.clear(); |
260 | if (server.is_valid()) { |
261 | server->stop(); |
262 | EditorNode::get_log()->add_message("--- Debugging process stopped ---" , EditorLog::MSG_TYPE_EDITOR); |
263 | |
264 | if (EditorRunBar::get_singleton()->is_movie_maker_enabled()) { |
265 | // Request attention in case the user was doing something else when movie recording is finished. |
266 | DisplayServer::get_singleton()->window_request_attention(); |
267 | } |
268 | |
269 | server.unref(); |
270 | } |
271 | // Also close all debugging sessions. |
272 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
273 | if (dbg->is_session_active()) { |
274 | dbg->_stop_and_notify(); |
275 | } |
276 | }); |
277 | _break_state_changed(); |
278 | breakpoints.clear(); |
279 | EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY); |
280 | set_process(false); |
281 | } |
282 | |
283 | void EditorDebuggerNode::_notification(int p_what) { |
284 | switch (p_what) { |
285 | case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { |
286 | if (tabs->get_tab_count() > 1) { |
287 | add_theme_constant_override("margin_left" , -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride" ), EditorStringName(EditorStyles))->get_margin(SIDE_LEFT)); |
288 | add_theme_constant_override("margin_right" , -EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("BottomPanelDebuggerOverride" ), EditorStringName(EditorStyles))->get_margin(SIDE_RIGHT)); |
289 | |
290 | tabs->add_theme_style_override("panel" , EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("DebuggerPanel" ), EditorStringName(EditorStyles))); |
291 | } |
292 | } break; |
293 | |
294 | case NOTIFICATION_READY: { |
295 | _update_debug_options(); |
296 | } break; |
297 | |
298 | case NOTIFICATION_PROCESS: { |
299 | if (!server.is_valid()) { |
300 | return; |
301 | } |
302 | |
303 | if (!server->is_active()) { |
304 | stop(); |
305 | return; |
306 | } |
307 | server->poll(); |
308 | |
309 | _update_errors(); |
310 | |
311 | // Remote scene tree update |
312 | remote_scene_tree_timeout -= get_process_delta_time(); |
313 | if (remote_scene_tree_timeout < 0) { |
314 | remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval" ); |
315 | if (remote_scene_tree->is_visible_in_tree()) { |
316 | get_current_debugger()->request_remote_tree(); |
317 | } |
318 | } |
319 | |
320 | // Remote inspector update |
321 | inspect_edited_object_timeout -= get_process_delta_time(); |
322 | if (inspect_edited_object_timeout < 0) { |
323 | inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval" ); |
324 | if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { |
325 | get_current_debugger()->request_remote_object(obj->remote_object_id); |
326 | } |
327 | } |
328 | |
329 | // Take connections. |
330 | if (server->is_connection_available()) { |
331 | ScriptEditorDebugger *debugger = nullptr; |
332 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
333 | if (debugger || dbg->is_session_active()) { |
334 | return; |
335 | } |
336 | debugger = dbg; |
337 | }); |
338 | if (debugger == nullptr) { |
339 | if (tabs->get_tab_count() <= 4) { // Max 4 debugging sessions active. |
340 | debugger = _add_debugger(); |
341 | } else { |
342 | // We already have too many sessions, disconnecting new clients to prevent them from hanging. |
343 | server->take_connection()->close(); |
344 | return; // Can't add, stop here. |
345 | } |
346 | } |
347 | |
348 | EditorRunBar::get_singleton()->get_pause_button()->set_disabled(false); |
349 | // Switch to remote tree view if so desired. |
350 | auto_switch_remote_scene_tree = (bool)EDITOR_GET("debugger/auto_switch_to_remote_scene_tree" ); |
351 | if (auto_switch_remote_scene_tree) { |
352 | SceneTreeDock::get_singleton()->show_remote_tree(); |
353 | } |
354 | // Good to go. |
355 | SceneTreeDock::get_singleton()->show_tab_buttons(); |
356 | debugger->set_editor_remote_tree(remote_scene_tree); |
357 | debugger->start(server->take_connection()); |
358 | // Send breakpoints. |
359 | for (const KeyValue<Breakpoint, bool> &E : breakpoints) { |
360 | const Breakpoint &bp = E.key; |
361 | debugger->set_breakpoint(bp.source, bp.line, E.value); |
362 | } // Will arrive too late, how does the regular run work? |
363 | |
364 | debugger->update_live_edit_root(); |
365 | } |
366 | } break; |
367 | } |
368 | } |
369 | |
370 | void EditorDebuggerNode::_update_errors() { |
371 | int error_count = 0; |
372 | int warning_count = 0; |
373 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
374 | error_count += dbg->get_error_count(); |
375 | warning_count += dbg->get_warning_count(); |
376 | }); |
377 | |
378 | if (error_count != last_error_count || warning_count != last_warning_count) { |
379 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
380 | dbg->update_tabs(); |
381 | }); |
382 | |
383 | if (error_count == 0 && warning_count == 0) { |
384 | debugger_button->set_text(TTR("Debugger" )); |
385 | debugger_button->remove_theme_color_override("font_color" ); |
386 | debugger_button->set_icon(Ref<Texture2D>()); |
387 | } else { |
388 | debugger_button->set_text(TTR("Debugger" ) + " (" + itos(error_count + warning_count) + ")" ); |
389 | if (error_count >= 1 && warning_count >= 1) { |
390 | debugger_button->set_icon(get_editor_theme_icon(SNAME("ErrorWarning" ))); |
391 | // Use error color to represent the highest level of severity reported. |
392 | debugger_button->add_theme_color_override("font_color" , get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
393 | } else if (error_count >= 1) { |
394 | debugger_button->set_icon(get_editor_theme_icon(SNAME("Error" ))); |
395 | debugger_button->add_theme_color_override("font_color" , get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
396 | } else { |
397 | debugger_button->set_icon(get_editor_theme_icon(SNAME("Warning" ))); |
398 | debugger_button->add_theme_color_override("font_color" , get_theme_color(SNAME("warning_color" ), EditorStringName(Editor))); |
399 | } |
400 | } |
401 | last_error_count = error_count; |
402 | last_warning_count = warning_count; |
403 | } |
404 | } |
405 | |
406 | void EditorDebuggerNode::_debugger_stopped(int p_id) { |
407 | ScriptEditorDebugger *dbg = get_debugger(p_id); |
408 | ERR_FAIL_NULL(dbg); |
409 | |
410 | bool found = false; |
411 | _for_all(tabs, [&](ScriptEditorDebugger *p_debugger) { |
412 | if (p_debugger->is_session_active()) { |
413 | found = true; |
414 | } |
415 | }); |
416 | if (!found) { |
417 | EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false); |
418 | EditorRunBar::get_singleton()->get_pause_button()->set_disabled(true); |
419 | SceneTreeDock::get_singleton()->hide_remote_tree(); |
420 | SceneTreeDock::get_singleton()->hide_tab_buttons(); |
421 | EditorNode::get_singleton()->notify_all_debug_sessions_exited(); |
422 | } |
423 | } |
424 | |
425 | void EditorDebuggerNode::_debugger_wants_stop(int p_id) { |
426 | // Ask editor to kill PID. |
427 | int pid = get_debugger(p_id)->get_remote_pid(); |
428 | if (pid) { |
429 | EditorNode::get_singleton()->call_deferred(SNAME("stop_child_process" ), pid); |
430 | } |
431 | } |
432 | |
433 | void EditorDebuggerNode::_debugger_changed(int p_tab) { |
434 | if (get_inspected_remote_object()) { |
435 | // Clear inspected object, you can only inspect objects in selected debugger. |
436 | // Hopefully, in the future, we will have one inspector per debugger. |
437 | EditorNode::get_singleton()->push_item(nullptr); |
438 | } |
439 | if (remote_scene_tree->is_visible_in_tree()) { |
440 | get_current_debugger()->request_remote_tree(); |
441 | } |
442 | if (get_current_debugger()->is_breaked()) { |
443 | _text_editor_stack_goto(get_current_debugger()); |
444 | } |
445 | } |
446 | |
447 | void EditorDebuggerNode::(MenuButton *p_button) { |
448 | script_menu = p_button; |
449 | script_menu->set_text(TTR("Debug" )); |
450 | script_menu->set_switch_on_hover(true); |
451 | PopupMenu *p = script_menu->get_popup(); |
452 | p->add_shortcut(ED_GET_SHORTCUT("debugger/step_into" ), DEBUG_STEP); |
453 | p->add_shortcut(ED_GET_SHORTCUT("debugger/step_over" ), DEBUG_NEXT); |
454 | p->add_separator(); |
455 | p->add_shortcut(ED_GET_SHORTCUT("debugger/break" ), DEBUG_BREAK); |
456 | p->add_shortcut(ED_GET_SHORTCUT("debugger/continue" ), DEBUG_CONTINUE); |
457 | p->add_separator(); |
458 | p->add_check_shortcut(ED_GET_SHORTCUT("debugger/debug_with_external_editor" ), DEBUG_WITH_EXTERNAL_EDITOR); |
459 | p->connect("id_pressed" , callable_mp(this, &EditorDebuggerNode::_menu_option)); |
460 | |
461 | _break_state_changed(); |
462 | script_menu->show(); |
463 | } |
464 | |
465 | void EditorDebuggerNode::_break_state_changed() { |
466 | const bool breaked = get_current_debugger()->is_breaked(); |
467 | const bool can_debug = get_current_debugger()->is_debuggable(); |
468 | if (breaked) { // Show debugger. |
469 | EditorNode::get_singleton()->make_bottom_panel_item_visible(this); |
470 | } |
471 | |
472 | // Update script menu. |
473 | if (!script_menu) { |
474 | return; |
475 | } |
476 | PopupMenu *p = script_menu->get_popup(); |
477 | p->set_item_disabled(p->get_item_index(DEBUG_NEXT), !(breaked && can_debug)); |
478 | p->set_item_disabled(p->get_item_index(DEBUG_STEP), !(breaked && can_debug)); |
479 | p->set_item_disabled(p->get_item_index(DEBUG_BREAK), breaked); |
480 | p->set_item_disabled(p->get_item_index(DEBUG_CONTINUE), !breaked); |
481 | } |
482 | |
483 | void EditorDebuggerNode::(int p_id) { |
484 | switch (p_id) { |
485 | case DEBUG_NEXT: { |
486 | debug_next(); |
487 | } break; |
488 | case DEBUG_STEP: { |
489 | debug_step(); |
490 | } break; |
491 | case DEBUG_BREAK: { |
492 | debug_break(); |
493 | } break; |
494 | case DEBUG_CONTINUE: { |
495 | debug_continue(); |
496 | } break; |
497 | case DEBUG_WITH_EXTERNAL_EDITOR: { |
498 | bool ischecked = script_menu->get_popup()->is_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR)); |
499 | debug_with_external_editor = !ischecked; |
500 | script_menu->get_popup()->set_item_checked(script_menu->get_popup()->get_item_index(DEBUG_WITH_EXTERNAL_EDITOR), !ischecked); |
501 | EditorSettings::get_singleton()->set_project_metadata("debug_options" , "debug_with_external_editor" , !ischecked); |
502 | } break; |
503 | } |
504 | } |
505 | |
506 | void EditorDebuggerNode::_update_debug_options() { |
507 | if (EditorSettings::get_singleton()->get_project_metadata("debug_options" , "debug_with_external_editor" , false).operator bool()) { |
508 | _menu_option(DEBUG_WITH_EXTERNAL_EDITOR); |
509 | } |
510 | } |
511 | |
512 | void EditorDebuggerNode::_paused() { |
513 | const bool paused = EditorRunBar::get_singleton()->get_pause_button()->is_pressed(); |
514 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
515 | if (paused && !dbg->is_breaked()) { |
516 | dbg->debug_break(); |
517 | } else if (!paused && dbg->is_breaked()) { |
518 | dbg->debug_continue(); |
519 | } |
520 | }); |
521 | } |
522 | |
523 | void EditorDebuggerNode::_breaked(bool p_breaked, bool p_can_debug, String p_message, bool p_has_stackdump, int p_debugger) { |
524 | if (get_current_debugger() != get_debugger(p_debugger)) { |
525 | if (!p_breaked) { |
526 | return; |
527 | } |
528 | tabs->set_current_tab(p_debugger); |
529 | } |
530 | _break_state_changed(); |
531 | EditorRunBar::get_singleton()->get_pause_button()->set_pressed(p_breaked); |
532 | emit_signal(SNAME("breaked" ), p_breaked, p_can_debug); |
533 | } |
534 | |
535 | bool EditorDebuggerNode::is_skip_breakpoints() const { |
536 | return get_default_debugger()->is_skip_breakpoints(); |
537 | } |
538 | |
539 | void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p_enabled) { |
540 | breakpoints[Breakpoint(p_path, p_line)] = p_enabled; |
541 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
542 | dbg->set_breakpoint(p_path, p_line, p_enabled); |
543 | }); |
544 | |
545 | emit_signal(SNAME("breakpoint_toggled" ), p_path, p_line, p_enabled); |
546 | } |
547 | |
548 | void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) { |
549 | for (int i = 0; i < p_lines.size(); i++) { |
550 | set_breakpoint(p_path, p_lines[i], true); |
551 | } |
552 | |
553 | for (const KeyValue<Breakpoint, bool> &E : breakpoints) { |
554 | Breakpoint b = E.key; |
555 | if (b.source == p_path && !p_lines.has(b.line)) { |
556 | set_breakpoint(p_path, b.line, false); |
557 | } |
558 | } |
559 | } |
560 | |
561 | void EditorDebuggerNode::reload_scripts() { |
562 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
563 | dbg->reload_scripts(); |
564 | }); |
565 | } |
566 | |
567 | void EditorDebuggerNode::debug_next() { |
568 | get_default_debugger()->debug_next(); |
569 | } |
570 | |
571 | void EditorDebuggerNode::debug_step() { |
572 | get_default_debugger()->debug_step(); |
573 | } |
574 | |
575 | void EditorDebuggerNode::debug_break() { |
576 | get_default_debugger()->debug_break(); |
577 | } |
578 | |
579 | void EditorDebuggerNode::debug_continue() { |
580 | get_default_debugger()->debug_continue(); |
581 | } |
582 | |
583 | String EditorDebuggerNode::get_var_value(const String &p_var) const { |
584 | return get_default_debugger()->get_var_value(p_var); |
585 | } |
586 | |
587 | // LiveEdit/Inspector |
588 | void EditorDebuggerNode::request_remote_tree() { |
589 | get_current_debugger()->request_remote_tree(); |
590 | } |
591 | |
592 | void EditorDebuggerNode::_remote_tree_updated(int p_debugger) { |
593 | if (p_debugger != tabs->get_current_tab()) { |
594 | return; |
595 | } |
596 | remote_scene_tree->clear(); |
597 | remote_scene_tree->update_scene_tree(get_current_debugger()->get_remote_tree(), p_debugger); |
598 | } |
599 | |
600 | void EditorDebuggerNode::_remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { |
601 | if (p_button != MouseButton::LEFT) { |
602 | return; |
603 | } |
604 | |
605 | TreeItem *item = Object::cast_to<TreeItem>(p_item); |
606 | ERR_FAIL_NULL(item); |
607 | |
608 | if (p_id == EditorDebuggerTree::BUTTON_SUBSCENE) { |
609 | remote_scene_tree->emit_signal(SNAME("open" ), item->get_meta("scene_file_path" )); |
610 | } else if (p_id == EditorDebuggerTree::BUTTON_VISIBILITY) { |
611 | ObjectID obj_id = item->get_metadata(0); |
612 | ERR_FAIL_COND(obj_id.is_null()); |
613 | get_current_debugger()->update_remote_object(obj_id, "visible" , !item->get_meta("visible" )); |
614 | get_current_debugger()->request_remote_tree(); |
615 | } |
616 | } |
617 | |
618 | void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) { |
619 | if (p_debugger != tabs->get_current_tab()) { |
620 | return; |
621 | } |
622 | if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { |
623 | if (obj->remote_object_id == p_id) { |
624 | return; // Already being edited |
625 | } |
626 | } |
627 | |
628 | EditorNode::get_singleton()->push_item(get_current_debugger()->get_remote_object(p_id)); |
629 | } |
630 | |
631 | void EditorDebuggerNode::_remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger) { |
632 | if (p_debugger != tabs->get_current_tab()) { |
633 | return; |
634 | } |
635 | if (EditorDebuggerRemoteObject *obj = get_inspected_remote_object()) { |
636 | if (obj->remote_object_id != p_id) { |
637 | return; |
638 | } |
639 | InspectorDock::get_inspector_singleton()->update_property(p_property); |
640 | } |
641 | } |
642 | |
643 | void EditorDebuggerNode::_remote_object_requested(ObjectID p_id, int p_debugger) { |
644 | if (p_debugger != tabs->get_current_tab()) { |
645 | return; |
646 | } |
647 | inspect_edited_object_timeout = 0.7; // Temporarily disable timeout to avoid multiple requests. |
648 | get_current_debugger()->request_remote_object(p_id); |
649 | } |
650 | |
651 | void EditorDebuggerNode::_save_node_requested(ObjectID p_id, const String &p_file, int p_debugger) { |
652 | if (p_debugger != tabs->get_current_tab()) { |
653 | return; |
654 | } |
655 | get_current_debugger()->save_node(p_id, p_file); |
656 | } |
657 | |
658 | // Remote inspector/edit. |
659 | void EditorDebuggerNode::_method_changeds(void *p_ud, Object *p_base, const StringName &p_name, const Variant **p_args, int p_argcount) { |
660 | if (!singleton) { |
661 | return; |
662 | } |
663 | _for_all(singleton->tabs, [&](ScriptEditorDebugger *dbg) { |
664 | dbg->_method_changed(p_base, p_name, p_args, p_argcount); |
665 | }); |
666 | } |
667 | |
668 | void EditorDebuggerNode::_property_changeds(void *p_ud, Object *p_base, const StringName &p_property, const Variant &p_value) { |
669 | if (!singleton) { |
670 | return; |
671 | } |
672 | _for_all(singleton->tabs, [&](ScriptEditorDebugger *dbg) { |
673 | dbg->_property_changed(p_base, p_property, p_value); |
674 | }); |
675 | } |
676 | |
677 | // LiveDebug |
678 | void EditorDebuggerNode::set_live_debugging(bool p_enabled) { |
679 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
680 | dbg->set_live_debugging(p_enabled); |
681 | }); |
682 | } |
683 | |
684 | void EditorDebuggerNode::update_live_edit_root() { |
685 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
686 | dbg->update_live_edit_root(); |
687 | }); |
688 | } |
689 | |
690 | void EditorDebuggerNode::live_debug_create_node(const NodePath &p_parent, const String &p_type, const String &p_name) { |
691 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
692 | dbg->live_debug_create_node(p_parent, p_type, p_name); |
693 | }); |
694 | } |
695 | |
696 | void EditorDebuggerNode::live_debug_instantiate_node(const NodePath &p_parent, const String &p_path, const String &p_name) { |
697 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
698 | dbg->live_debug_instantiate_node(p_parent, p_path, p_name); |
699 | }); |
700 | } |
701 | |
702 | void EditorDebuggerNode::live_debug_remove_node(const NodePath &p_at) { |
703 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
704 | dbg->live_debug_remove_node(p_at); |
705 | }); |
706 | } |
707 | |
708 | void EditorDebuggerNode::live_debug_remove_and_keep_node(const NodePath &p_at, ObjectID p_keep_id) { |
709 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
710 | dbg->live_debug_remove_and_keep_node(p_at, p_keep_id); |
711 | }); |
712 | } |
713 | |
714 | void EditorDebuggerNode::live_debug_restore_node(ObjectID p_id, const NodePath &p_at, int p_at_pos) { |
715 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
716 | dbg->live_debug_restore_node(p_id, p_at, p_at_pos); |
717 | }); |
718 | } |
719 | |
720 | void EditorDebuggerNode::live_debug_duplicate_node(const NodePath &p_at, const String &p_new_name) { |
721 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
722 | dbg->live_debug_duplicate_node(p_at, p_new_name); |
723 | }); |
724 | } |
725 | |
726 | void EditorDebuggerNode::live_debug_reparent_node(const NodePath &p_at, const NodePath &p_new_place, const String &p_new_name, int p_at_pos) { |
727 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
728 | dbg->live_debug_reparent_node(p_at, p_new_place, p_new_name, p_at_pos); |
729 | }); |
730 | } |
731 | |
732 | void EditorDebuggerNode::set_camera_override(CameraOverride p_override) { |
733 | _for_all(tabs, [&](ScriptEditorDebugger *dbg) { |
734 | dbg->set_camera_override(p_override); |
735 | }); |
736 | camera_override = p_override; |
737 | } |
738 | |
739 | EditorDebuggerNode::CameraOverride EditorDebuggerNode::get_camera_override() { |
740 | return camera_override; |
741 | } |
742 | |
743 | void EditorDebuggerNode::add_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { |
744 | ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null." ); |
745 | ERR_FAIL_COND_MSG(debugger_plugins.has(p_plugin), "Debugger plugin already exists." ); |
746 | debugger_plugins.insert(p_plugin); |
747 | |
748 | Ref<EditorDebuggerPlugin> plugin = p_plugin; |
749 | for (int i = 0; get_debugger(i); i++) { |
750 | plugin->create_session(get_debugger(i)); |
751 | } |
752 | } |
753 | |
754 | void EditorDebuggerNode::remove_debugger_plugin(const Ref<EditorDebuggerPlugin> &p_plugin) { |
755 | ERR_FAIL_COND_MSG(p_plugin.is_null(), "Debugger plugin is null." ); |
756 | ERR_FAIL_COND_MSG(!debugger_plugins.has(p_plugin), "Debugger plugin doesn't exists." ); |
757 | debugger_plugins.erase(p_plugin); |
758 | Ref<EditorDebuggerPlugin>(p_plugin)->clear(); |
759 | } |
760 | |
761 | bool EditorDebuggerNode::plugins_capture(ScriptEditorDebugger *p_debugger, const String &p_message, const Array &p_data) { |
762 | int session_index = tabs->get_tab_idx_from_control(p_debugger); |
763 | ERR_FAIL_COND_V(session_index < 0, false); |
764 | int colon_index = p_message.find_char(':'); |
765 | ERR_FAIL_COND_V_MSG(colon_index < 1, false, "Invalid message received." ); |
766 | |
767 | const String cap = p_message.substr(0, colon_index); |
768 | bool parsed = false; |
769 | for (Ref<EditorDebuggerPlugin> plugin : debugger_plugins) { |
770 | if (plugin->has_capture(cap)) { |
771 | parsed |= plugin->capture(p_message, p_data, session_index); |
772 | } |
773 | } |
774 | return parsed; |
775 | } |
776 | |