浏览代码

Add expression evaluater to debugger (REPL)

Co-authored-by: rohanrhu <[email protected]>
kobewi 1 年之前
父节点
当前提交
645abdbb80

+ 36 - 0
core/debugger/remote_debugger.cpp

@@ -37,6 +37,7 @@
 #include "core/debugger/script_debugger.h"
 #include "core/input/input.h"
 #include "core/io/resource_loader.h"
+#include "core/math/expression.h"
 #include "core/object/script_language.h"
 #include "core/os/os.h"
 #include "servers/display_server.h"
@@ -529,6 +530,41 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
 			} else if (command == "set_skip_breakpoints") {
 				ERR_FAIL_COND(data.is_empty());
 				script_debugger->set_skip_breakpoints(data[0]);
+			} else if (command == "evaluate") {
+				String expression_str = data[0];
+				int frame = data[1];
+
+				ScriptInstance *breaked_instance = script_debugger->get_break_language()->debug_get_stack_level_instance(frame);
+				if (!breaked_instance) {
+					break;
+				}
+
+				List<String> locals;
+				List<Variant> local_vals;
+
+				script_debugger->get_break_language()->debug_get_stack_level_locals(frame, &locals, &local_vals);
+				ERR_FAIL_COND(locals.size() != local_vals.size());
+
+				PackedStringArray locals_vector;
+				for (const String &S : locals) {
+					locals_vector.append(S);
+				}
+
+				Array local_vals_array;
+				for (const Variant &V : local_vals) {
+					local_vals_array.append(V);
+				}
+
+				Expression expression;
+				expression.parse(expression_str, locals_vector);
+				const Variant return_val = expression.execute(local_vals_array, breaked_instance->get_owner());
+
+				DebuggerMarshalls::ScriptStackVariable stvar;
+				stvar.name = expression_str;
+				stvar.value = return_val;
+				stvar.type = 3;
+
+				send_message("evaluation_return", stvar.serialize());
 			} else {
 				bool captured = false;
 				ERR_CONTINUE(_try_capture(command, data, captured) != OK);

+ 1 - 1
editor/debugger/debug_adapter/debug_adapter_protocol.cpp

@@ -966,7 +966,7 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
 
 	List<int> scope_ids = stackframe_list.find(frame)->value;
 	ERR_FAIL_COND(scope_ids.size() != 3);
-	ERR_FAIL_INDEX(stack_var.type, 3);
+	ERR_FAIL_INDEX(stack_var.type, 4);
 	int var_id = scope_ids.get(stack_var.type);
 
 	DAP::Variable variable;

+ 13 - 2
editor/debugger/editor_debugger_inspector.cpp

@@ -223,7 +223,7 @@ Object *EditorDebuggerInspector::get_object(ObjectID p_id) {
 	return nullptr;
 }
 
-void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
+void EditorDebuggerInspector::add_stack_variable(const Array &p_array, int p_offset) {
 	DebuggerMarshalls::ScriptStackVariable var;
 	var.deserialize(p_array);
 	String n = var.name;
@@ -248,6 +248,9 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
 		case 2:
 			type = "Globals/";
 			break;
+		case 3:
+			type = "Evaluated/";
+			break;
 		default:
 			type = "Unknown/";
 	}
@@ -258,7 +261,15 @@ void EditorDebuggerInspector::add_stack_variable(const Array &p_array) {
 	pinfo.hint = h;
 	pinfo.hint_string = hs;
 
-	variables->prop_list.push_back(pinfo);
+	if ((p_offset == -1) || variables->prop_list.is_empty()) {
+		variables->prop_list.push_back(pinfo);
+	} else {
+		List<PropertyInfo>::Element *current = variables->prop_list.front();
+		for (int i = 0; i < p_offset; i++) {
+			current = current->next();
+		}
+		variables->prop_list.insert_before(current, pinfo);
+	}
 	variables->prop_values[type + n] = v;
 	variables->update();
 	edit(variables);

+ 1 - 1
editor/debugger/editor_debugger_inspector.h

@@ -90,7 +90,7 @@ public:
 
 	// Stack Dump variables
 	String get_stack_variable(const String &p_var);
-	void add_stack_variable(const Array &p_arr);
+	void add_stack_variable(const Array &p_arr, int p_offset = -1);
 	void clear_stack_variables();
 };
 

+ 148 - 0
editor/debugger/editor_expression_evaluator.cpp

@@ -0,0 +1,148 @@
+/**************************************************************************/
+/*  editor_expression_evaluator.cpp                                       */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#include "editor_expression_evaluator.h"
+
+#include "editor/debugger/editor_debugger_inspector.h"
+#include "editor/debugger/script_editor_debugger.h"
+#include "scene/gui/button.h"
+#include "scene/gui/check_box.h"
+
+void EditorExpressionEvaluator::on_start() {
+	expression_input->set_editable(false);
+	evaluate_btn->set_disabled(true);
+
+	if (clear_on_run_checkbox->is_pressed()) {
+		inspector->clear_stack_variables();
+	}
+}
+
+void EditorExpressionEvaluator::set_editor_debugger(ScriptEditorDebugger *p_editor_debugger) {
+	editor_debugger = p_editor_debugger;
+}
+
+void EditorExpressionEvaluator::add_value(const Array &p_array) {
+	inspector->add_stack_variable(p_array, 0);
+	inspector->set_v_scroll(0);
+	inspector->set_h_scroll(0);
+}
+
+void EditorExpressionEvaluator::_evaluate() {
+	const String &expression = expression_input->get_text();
+	if (expression.is_empty()) {
+		return;
+	}
+
+	if (!editor_debugger->is_session_active()) {
+		return;
+	}
+
+	Array expr_data;
+	expr_data.push_back(expression);
+	expr_data.push_back(editor_debugger->get_stack_script_frame());
+	editor_debugger->send_message("evaluate", expr_data);
+
+	expression_input->clear();
+}
+
+void EditorExpressionEvaluator::_clear() {
+	inspector->clear_stack_variables();
+}
+
+void EditorExpressionEvaluator::_remote_object_selected(ObjectID p_id) {
+	editor_debugger->emit_signal(SNAME("remote_object_requested"), p_id);
+}
+
+void EditorExpressionEvaluator::_on_expression_input_changed(const String &p_expression) {
+	evaluate_btn->set_disabled(p_expression.is_empty());
+}
+
+void EditorExpressionEvaluator::_on_debugger_breaked(bool p_breaked, bool p_can_debug) {
+	expression_input->set_editable(p_breaked);
+	evaluate_btn->set_disabled(!p_breaked);
+}
+
+void EditorExpressionEvaluator::_on_debugger_clear_execution(Ref<Script> p_stack_script) {
+	expression_input->set_editable(false);
+	evaluate_btn->set_disabled(true);
+}
+
+void EditorExpressionEvaluator::_notification(int p_what) {
+	switch (p_what) {
+		case NOTIFICATION_READY: {
+			EditorDebuggerNode::get_singleton()->connect("breaked", callable_mp(this, &EditorExpressionEvaluator::_on_debugger_breaked));
+			EditorDebuggerNode::get_singleton()->connect("clear_execution", callable_mp(this, &EditorExpressionEvaluator::_on_debugger_clear_execution));
+		} break;
+	}
+}
+
+EditorExpressionEvaluator::EditorExpressionEvaluator() {
+	set_h_size_flags(SIZE_EXPAND_FILL);
+
+	HBoxContainer *hb = memnew(HBoxContainer);
+	add_child(hb);
+
+	expression_input = memnew(LineEdit);
+	expression_input->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+	expression_input->set_placeholder(TTR("Expression to evaluate"));
+	expression_input->set_clear_button_enabled(true);
+	expression_input->connect("text_submitted", callable_mp(this, &EditorExpressionEvaluator::_evaluate).unbind(1));
+	expression_input->connect(SceneStringName(text_changed), callable_mp(this, &EditorExpressionEvaluator::_on_expression_input_changed));
+	hb->add_child(expression_input);
+
+	clear_on_run_checkbox = memnew(CheckBox);
+	clear_on_run_checkbox->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+	clear_on_run_checkbox->set_text(TTR("Clear on Run"));
+	clear_on_run_checkbox->set_pressed(true);
+	hb->add_child(clear_on_run_checkbox);
+
+	evaluate_btn = memnew(Button);
+	evaluate_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+	evaluate_btn->set_text(TTR("Evaluate"));
+	evaluate_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorExpressionEvaluator::_evaluate));
+	hb->add_child(evaluate_btn);
+
+	clear_btn = memnew(Button);
+	clear_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+	clear_btn->set_text(TTR("Clear"));
+	clear_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorExpressionEvaluator::_clear));
+	hb->add_child(clear_btn);
+
+	inspector = memnew(EditorDebuggerInspector);
+	inspector->set_v_size_flags(SIZE_EXPAND_FILL);
+	inspector->set_property_name_style(EditorPropertyNameProcessor::STYLE_RAW);
+	inspector->set_read_only(true);
+	inspector->connect("object_selected", callable_mp(this, &EditorExpressionEvaluator::_remote_object_selected));
+	inspector->set_use_filter(true);
+	add_child(inspector);
+
+	expression_input->set_editable(false);
+	evaluate_btn->set_disabled(true);
+}

+ 77 - 0
editor/debugger/editor_expression_evaluator.h

@@ -0,0 +1,77 @@
+/**************************************************************************/
+/*  editor_expression_evaluator.h                                         */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef EDITOR_EXPRESSION_EVALUATOR_H
+#define EDITOR_EXPRESSION_EVALUATOR_H
+
+#include "scene/gui/box_container.h"
+
+class Button;
+class CheckBox;
+class EditorDebuggerInspector;
+class LineEdit;
+class RemoteDebuggerPeer;
+class ScriptEditorDebugger;
+
+class EditorExpressionEvaluator : public VBoxContainer {
+	GDCLASS(EditorExpressionEvaluator, VBoxContainer)
+
+private:
+	Ref<RemoteDebuggerPeer> peer;
+
+	LineEdit *expression_input = nullptr;
+	CheckBox *clear_on_run_checkbox = nullptr;
+	Button *evaluate_btn = nullptr;
+	Button *clear_btn = nullptr;
+
+	EditorDebuggerInspector *inspector = nullptr;
+
+	void _evaluate();
+	void _clear();
+
+	void _remote_object_selected(ObjectID p_id);
+	void _on_expression_input_changed(const String &p_expression);
+	void _on_debugger_breaked(bool p_breaked, bool p_can_debug);
+	void _on_debugger_clear_execution(Ref<Script> p_stack_script);
+
+protected:
+	ScriptEditorDebugger *editor_debugger = nullptr;
+
+	void _notification(int p_what);
+
+public:
+	void on_start();
+	void set_editor_debugger(ScriptEditorDebugger *p_editor_debugger);
+	void add_value(const Array &p_array);
+
+	EditorExpressionEvaluator();
+};
+
+#endif // EDITOR_EXPRESSION_EVALUATOR_H

+ 13 - 2
editor/debugger/script_editor_debugger.cpp

@@ -37,6 +37,7 @@
 #include "core/string/ustring.h"
 #include "core/version.h"
 #include "editor/debugger/debug_adapter/debug_adapter_protocol.h"
+#include "editor/debugger/editor_expression_evaluator.h"
 #include "editor/debugger/editor_performance_profiler.h"
 #include "editor/debugger/editor_profiler.h"
 #include "editor/debugger/editor_visual_profiler.h"
@@ -811,6 +812,8 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, uint64_t p_thread
 		if (EditorFileSystem::get_singleton()) {
 			EditorFileSystem::get_singleton()->update_file(p_data[0]);
 		}
+	} else if (p_msg == "evaluation_return") {
+		expression_evaluator->add_value(p_data);
 	} else {
 		int colon_index = p_msg.find_char(':');
 		ERR_FAIL_COND_MSG(colon_index < 1, "Invalid message received");
@@ -854,8 +857,9 @@ void ScriptEditorDebugger::_notification(int p_what) {
 			error_tree->connect(SceneStringName(item_selected), callable_mp(this, &ScriptEditorDebugger::_error_selected));
 			error_tree->connect("item_activated", callable_mp(this, &ScriptEditorDebugger::_error_activated));
 			breakpoints_tree->connect("item_activated", callable_mp(this, &ScriptEditorDebugger::_breakpoint_tree_clicked));
-			[[fallthrough]];
-		}
+			connect("started", callable_mp(expression_evaluator, &EditorExpressionEvaluator::on_start));
+		} break;
+
 		case NOTIFICATION_THEME_CHANGED: {
 			tabs->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("DebuggerPanel"), EditorStringName(EditorStyles)));
 
@@ -2010,6 +2014,13 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
 		add_child(file_dialog);
 	}
 
+	{ // Expression evaluator
+		expression_evaluator = memnew(EditorExpressionEvaluator);
+		expression_evaluator->set_name(TTR("Evaluator"));
+		expression_evaluator->set_editor_debugger(this);
+		tabs->add_child(expression_evaluator);
+	}
+
 	{ //profiler
 		profiler = memnew(EditorProfiler);
 		profiler->set_name(TTR("Profiler"));

+ 2 - 0
editor/debugger/script_editor_debugger.h

@@ -56,6 +56,7 @@ class SceneDebuggerTree;
 class EditorDebuggerPlugin;
 class DebugAdapterProtocol;
 class DebugAdapterParser;
+class EditorExpressionEvaluator;
 
 class ScriptEditorDebugger : public MarginContainer {
 	GDCLASS(ScriptEditorDebugger, MarginContainer);
@@ -152,6 +153,7 @@ private:
 	EditorProfiler *profiler = nullptr;
 	EditorVisualProfiler *visual_profiler = nullptr;
 	EditorPerformanceProfiler *performance_profiler = nullptr;
+	EditorExpressionEvaluator *expression_evaluator = nullptr;
 
 	OS::ProcessID remote_pid = 0;
 	bool move_to_foreground = true;