Implemented advanced features of DAP

Respect client "supportsVariableType" capability

Implement "breakpointLocations" request

Implement "restart" request

Implement "evaluate" request

Fix error messages not being shown, and improved wrong path message

Removed thread option and behavior

Implemented detailed inspection of complex variables

Fix "const"ness of functions

Added a configurable timeout for requests

Implement Godot custom data request/event

Implement syncing of breakpoints

Added support for debugging native platforms
This commit is contained in:
Ev1lbl0w 2021-07-20 12:24:56 +01:00 committed by Ricardo Subtil
parent d8a8d32f2e
commit 292ed61c18
20 changed files with 845 additions and 67 deletions

View File

@ -33,12 +33,15 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
#include "editor/editor_run_native.h"
void DebugAdapterParser::_bind_methods() {
// Requests
ClassDB::bind_method(D_METHOD("req_initialize", "params"), &DebugAdapterParser::req_initialize);
ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::prepare_success_response);
ClassDB::bind_method(D_METHOD("req_disconnect", "params"), &DebugAdapterParser::req_disconnect);
ClassDB::bind_method(D_METHOD("req_launch", "params"), &DebugAdapterParser::req_launch);
ClassDB::bind_method(D_METHOD("req_attach", "params"), &DebugAdapterParser::req_attach);
ClassDB::bind_method(D_METHOD("req_restart", "params"), &DebugAdapterParser::req_restart);
ClassDB::bind_method(D_METHOD("req_terminate", "params"), &DebugAdapterParser::req_terminate);
ClassDB::bind_method(D_METHOD("req_configurationDone", "params"), &DebugAdapterParser::prepare_success_response);
ClassDB::bind_method(D_METHOD("req_pause", "params"), &DebugAdapterParser::req_pause);
@ -46,10 +49,13 @@ void DebugAdapterParser::_bind_methods() {
ClassDB::bind_method(D_METHOD("req_threads", "params"), &DebugAdapterParser::req_threads);
ClassDB::bind_method(D_METHOD("req_stackTrace", "params"), &DebugAdapterParser::req_stackTrace);
ClassDB::bind_method(D_METHOD("req_setBreakpoints", "params"), &DebugAdapterParser::req_setBreakpoints);
ClassDB::bind_method(D_METHOD("req_breakpointLocations", "params"), &DebugAdapterParser::req_breakpointLocations);
ClassDB::bind_method(D_METHOD("req_scopes", "params"), &DebugAdapterParser::req_scopes);
ClassDB::bind_method(D_METHOD("req_variables", "params"), &DebugAdapterParser::req_variables);
ClassDB::bind_method(D_METHOD("req_next", "params"), &DebugAdapterParser::req_next);
ClassDB::bind_method(D_METHOD("req_stepIn", "params"), &DebugAdapterParser::req_stepIn);
ClassDB::bind_method(D_METHOD("req_evaluate", "params"), &DebugAdapterParser::req_evaluate);
ClassDB::bind_method(D_METHOD("req_godot/put_msg", "params"), &DebugAdapterParser::req_godot_put_msg);
}
Dictionary DebugAdapterParser::prepare_base_event() const {
@ -80,13 +86,31 @@ Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params
DAP::Message message;
String error, error_desc;
switch (err_type) {
case DAP::ErrorType::UNKNOWN:
error = "unknown";
error_desc = "An unknown error has ocurred when processing the request.";
break;
case DAP::ErrorType::WRONG_PATH:
error = "wrong_path";
error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"";
break;
case DAP::ErrorType::NOT_RUNNING:
error = "not_running";
error_desc = "Can't attach to a running session since there isn't one.";
break;
case DAP::ErrorType::TIMEOUT:
error = "timeout";
error_desc = "Timeout reached while processing a request.";
break;
case DAP::ErrorType::UNKNOWN_PLATFORM:
error = "unknown_platform";
error_desc = "The specified platform is unknown.";
break;
case DAP::ErrorType::MISSING_DEVICE:
error = "missing_device";
error_desc = "There's no connected device with specified id.";
break;
case DAP::ErrorType::UNKNOWN:
default:
error = "unknown";
error_desc = "An unknown error has ocurred when processing the request.";
break;
}
message.id = err_type;
@ -114,10 +138,35 @@ Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const
DebugAdapterProtocol::get_singleton()->notify_initialized();
if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) {
// Send all current breakpoints
List<String> breakpoints;
ScriptEditor::get_singleton()->get_breakpoints(&breakpoints);
for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) {
String breakpoint = E->get();
String path = breakpoint.left(breakpoint.find(":", 6)); // Skip initial part of path, aka "res://"
int line = breakpoint.substr(path.size()).to_int();
DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true);
}
} else {
// Remove all current breakpoints
EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints();
}
return response;
}
Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const {
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) {
EditorNode::get_singleton()->run_stop();
}
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const {
Dictionary args = p_params["arguments"];
if (args.has("project") && !is_valid_path(args["project"])) {
Dictionary variables;
@ -126,17 +175,85 @@ Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) {
return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables);
}
if (args.has("godot/custom_data")) {
DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data"];
}
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
if ((bool)args["noDebug"] != dbg->is_skip_breakpoints()) {
dbg->debug_skip_breakpoints();
}
EditorNode::get_singleton()->run_play();
String platform_string = args.get("platform", "host");
if (platform_string == "host") {
EditorNode::get_singleton()->run_play();
} else {
int device = args.get("device", -1);
int idx = -1;
if (platform_string == "android") {
for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android") {
idx = i;
break;
}
}
} else if (platform_string == "web") {
for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) {
if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "HTML5") {
idx = i;
break;
}
}
}
if (idx == -1) {
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM);
}
EditorNode *editor = EditorNode::get_singleton();
Error err = platform_string == "android" ? editor->run_play_native(device, idx) : editor->run_play_native(-1, idx);
if (err) {
if (err == ERR_INVALID_PARAMETER && platform_string == "android") {
return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE);
} else {
return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN);
}
}
}
DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false;
DebugAdapterProtocol::get_singleton()->notify_process();
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const {
ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger();
if (!dbg->is_session_active()) {
return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING);
}
DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true;
DebugAdapterProtocol::get_singleton()->notify_process();
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const {
// Extract embedded "arguments" so it can be given to req_launch/req_attach
Dictionary params = p_params, args;
args = params["arguments"];
args = args["arguments"];
params["arguments"] = args;
Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params);
if (!response["success"]) {
response["command"] = p_params["command"];
return response;
}
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const {
EditorNode::get_singleton()->run_stop();
@ -205,7 +322,7 @@ Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const
return response;
}
Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@ -230,14 +347,30 @@ Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) {
lines.push_back(breakpoint.line + !lines_at_one);
}
EditorDebuggerNode::get_singleton()->set_breakpoints(ProjectSettings::get_singleton()->localize_path(source.path), lines);
Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines);
body["breakpoints"] = updated_breakpoints;
return response;
}
Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) {
Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
Dictionary args = p_params["arguments"];
Array locations;
DAP::BreakpointLocation location;
location.line = args["line"];
if (args.has("endLine")) {
location.endLine = args["endLine"];
}
locations.push_back(location.to_json());
body["breakpoints"] = locations;
return response;
}
Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
@ -291,7 +424,14 @@ Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const {
int variable_id = args["variablesReference"];
Map<int, Array>::Element *E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id);
if (E) {
if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) {
for (int i = 0; i < E->value().size(); i++) {
Dictionary variable = E->value()[i];
variable.erase("type");
}
}
body["variables"] = E ? E->value() : Array();
return response;
} else {
@ -313,6 +453,29 @@ Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const {
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const {
Dictionary response = prepare_success_response(p_params), body;
response["body"] = body;
Dictionary args = p_params["arguments"];
String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression"]);
body["result"] = value;
return response;
}
Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const {
Dictionary args = p_params["arguments"];
String msg = args["message"];
Array data = args["data"];
EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data);
return prepare_success_response(p_params);
}
Dictionary DebugAdapterParser::ev_initialized() const {
Dictionary event = prepare_base_event();
event["event"] = "initialized";
@ -423,3 +586,25 @@ Dictionary DebugAdapterParser::ev_output(const String &p_message) const {
return event;
}
Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const {
Dictionary event = prepare_base_event(), body;
event["event"] = "breakpoint";
event["body"] = body;
body["reason"] = p_enabled ? "new" : "removed";
body["breakpoint"] = p_breakpoint.to_json();
return event;
}
Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const {
Dictionary event = prepare_base_event(), body;
event["event"] = "godot/custom_data";
event["body"] = body;
body["message"] = p_msg;
body["data"] = p_data;
return event;
}

View File

@ -44,7 +44,7 @@ class DebugAdapterParser : public Object {
private:
friend DebugAdapterProtocol;
_FORCE_INLINE_ bool is_valid_path(const String &p_path) {
_FORCE_INLINE_ bool is_valid_path(const String &p_path) const {
return p_path.begins_with(ProjectSettings::get_singleton()->get_resource_path());
}
@ -60,17 +60,23 @@ protected:
public:
// Requests
Dictionary req_initialize(const Dictionary &p_params) const;
Dictionary req_launch(const Dictionary &p_params);
Dictionary req_launch(const Dictionary &p_params) const;
Dictionary req_disconnect(const Dictionary &p_params) const;
Dictionary req_attach(const Dictionary &p_params) const;
Dictionary req_restart(const Dictionary &p_params) const;
Dictionary req_terminate(const Dictionary &p_params) const;
Dictionary req_pause(const Dictionary &p_params) const;
Dictionary req_continue(const Dictionary &p_params) const;
Dictionary req_threads(const Dictionary &p_params) const;
Dictionary req_stackTrace(const Dictionary &p_params) const;
Dictionary req_setBreakpoints(const Dictionary &p_params);
Dictionary req_scopes(const Dictionary &p_params);
Dictionary req_setBreakpoints(const Dictionary &p_params) const;
Dictionary req_breakpointLocations(const Dictionary &p_params) const;
Dictionary req_scopes(const Dictionary &p_params) const;
Dictionary req_variables(const Dictionary &p_params) const;
Dictionary req_next(const Dictionary &p_params) const;
Dictionary req_stepIn(const Dictionary &p_params) const;
Dictionary req_evaluate(const Dictionary &p_params) const;
Dictionary req_godot_put_msg(const Dictionary &p_params) const;
// Events
Dictionary ev_initialized() const;
@ -83,6 +89,8 @@ public:
Dictionary ev_stopped_step() const;
Dictionary ev_continued() const;
Dictionary ev_output(const String &p_message) const;
Dictionary ev_custom_data(const String &p_msg, const Array &p_data) const;
Dictionary ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const;
};
#endif

View File

@ -94,11 +94,17 @@ Error DAPeer::handle_data() {
String msg;
msg.parse_utf8((const char *)req_buf, req_pos);
// Apply a timestamp if it there's none yet
if (!timestamp) {
timestamp = OS::get_singleton()->get_ticks_msec();
}
// Response
if (DebugAdapterProtocol::get_singleton()->process_message(msg)) {
// Reset to read again
req_pos = 0;
has_header = false;
timestamp = 0;
}
}
return OK;
@ -180,12 +186,460 @@ void DebugAdapterProtocol::reset_stack_info() {
variable_list.clear();
}
int DebugAdapterProtocol::parse_variant(const Variant &p_var) {
switch (p_var.get_type()) {
case Variant::VECTOR2:
case Variant::VECTOR2I: {
int id = variable_id++;
Vector2 vec = p_var;
DAP::Variable x, y;
x.name = "x";
y.name = "y";
x.type = y.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR2 ? Variant::FLOAT : Variant::INT);
x.value = rtos(vec.x);
y.value = rtos(vec.y);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::RECT2:
case Variant::RECT2I: {
int id = variable_id++;
Rect2 rect = p_var;
DAP::Variable x, y, w, h;
x.name = "x";
y.name = "y";
w.name = "w";
h.name = "h";
x.type = y.type = w.type = h.type = Variant::get_type_name(p_var.get_type() == Variant::RECT2 ? Variant::FLOAT : Variant::INT);
x.value = rtos(rect.position.x);
y.value = rtos(rect.position.y);
w.value = rtos(rect.size.x);
h.value = rtos(rect.size.y);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
arr.push_back(w.to_json());
arr.push_back(h.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::VECTOR3:
case Variant::VECTOR3I: {
int id = variable_id++;
Vector3 vec = p_var;
DAP::Variable x, y, z;
x.name = "x";
y.name = "y";
z.name = "z";
x.type = y.type = z.type = Variant::get_type_name(p_var.get_type() == Variant::VECTOR3 ? Variant::FLOAT : Variant::INT);
x.value = rtos(vec.x);
y.value = rtos(vec.y);
z.value = rtos(vec.z);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
arr.push_back(z.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::TRANSFORM2D: {
int id = variable_id++;
Transform2D transform = p_var;
DAP::Variable x, y, origin;
x.name = "x";
y.name = "y";
origin.name = "origin";
x.type = y.type = origin.type = Variant::get_type_name(Variant::VECTOR2);
x.value = transform.elements[0];
y.value = transform.elements[1];
origin.value = transform.elements[2];
x.variablesReference = parse_variant(transform.elements[0]);
y.variablesReference = parse_variant(transform.elements[1]);
origin.variablesReference = parse_variant(transform.elements[2]);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
arr.push_back(origin.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::PLANE: {
int id = variable_id++;
Plane plane = p_var;
DAP::Variable d, normal;
d.name = "d";
normal.name = "normal";
d.type = Variant::get_type_name(Variant::FLOAT);
normal.type = Variant::get_type_name(Variant::VECTOR3);
d.value = rtos(plane.d);
normal.value = plane.normal;
normal.variablesReference = parse_variant(plane.normal);
Array arr;
arr.push_back(d.to_json());
arr.push_back(normal.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::QUATERNION: {
int id = variable_id++;
Quaternion quat = p_var;
DAP::Variable x, y, z, w;
x.name = "x";
y.name = "y";
z.name = "z";
w.name = "w";
x.type = y.type = z.type = w.type = Variant::get_type_name(Variant::FLOAT);
x.value = rtos(quat.x);
y.value = rtos(quat.y);
z.value = rtos(quat.z);
w.value = rtos(quat.w);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
arr.push_back(z.to_json());
arr.push_back(w.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::AABB: {
int id = variable_id++;
AABB aabb = p_var;
DAP::Variable position, size;
position.name = "position";
size.name = "size";
position.type = size.type = Variant::get_type_name(Variant::VECTOR3);
position.value = aabb.position;
size.value = aabb.size;
position.variablesReference = parse_variant(aabb.position);
size.variablesReference = parse_variant(aabb.size);
Array arr;
arr.push_back(position.to_json());
arr.push_back(size.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::BASIS: {
int id = variable_id++;
Basis basis = p_var;
DAP::Variable x, y, z;
x.name = "x";
y.name = "y";
z.name = "z";
x.type = y.type = z.type = Variant::get_type_name(Variant::VECTOR2);
x.value = basis.elements[0];
y.value = basis.elements[1];
z.value = basis.elements[2];
x.variablesReference = parse_variant(basis.elements[0]);
y.variablesReference = parse_variant(basis.elements[1]);
z.variablesReference = parse_variant(basis.elements[2]);
Array arr;
arr.push_back(x.to_json());
arr.push_back(y.to_json());
arr.push_back(z.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::TRANSFORM3D: {
int id = variable_id++;
Transform3D transform = p_var;
DAP::Variable basis, origin;
basis.name = "basis";
origin.name = "origin";
basis.type = Variant::get_type_name(Variant::BASIS);
origin.type = Variant::get_type_name(Variant::VECTOR3);
basis.value = transform.basis;
origin.value = transform.origin;
basis.variablesReference = parse_variant(transform.basis);
origin.variablesReference = parse_variant(transform.origin);
Array arr;
arr.push_back(basis.to_json());
arr.push_back(origin.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::COLOR: {
int id = variable_id++;
Color color = p_var;
DAP::Variable r, g, b, a;
r.name = "r";
g.name = "g";
b.name = "b";
a.name = "a";
r.type = g.type = b.type = a.type = Variant::get_type_name(Variant::FLOAT);
r.value = rtos(color.r);
g.value = rtos(color.g);
b.value = rtos(color.b);
a.value = rtos(color.a);
Array arr;
arr.push_back(r.to_json());
arr.push_back(g.to_json());
arr.push_back(b.to_json());
arr.push_back(a.to_json());
variable_list.insert(id, arr);
return id;
}
case Variant::ARRAY: {
int id = variable_id++;
Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = Variant::get_type_name(array[i].get_type());
var.value = array[i];
var.variablesReference = parse_variant(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::DICTIONARY: {
int id = variable_id++;
Dictionary dictionary = p_var;
Array arr;
for (int i = 0; i < dictionary.size(); i++) {
DAP::Variable var;
var.name = dictionary.get_key_at_index(i);
Variant value = dictionary.get_value_at_index(i);
var.type = Variant::get_type_name(value.get_type());
var.value = value;
var.variablesReference = parse_variant(value);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_BYTE_ARRAY: {
int id = variable_id++;
PackedByteArray array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = "byte";
var.value = itos(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_INT32_ARRAY: {
int id = variable_id++;
PackedInt32Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = "int";
var.value = itos(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_INT64_ARRAY: {
int id = variable_id++;
PackedInt64Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = "long";
var.value = itos(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_FLOAT32_ARRAY: {
int id = variable_id++;
PackedFloat32Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = "float";
var.value = rtos(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_FLOAT64_ARRAY: {
int id = variable_id++;
PackedFloat64Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = "double";
var.value = rtos(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_STRING_ARRAY: {
int id = variable_id++;
PackedStringArray array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = Variant::get_type_name(Variant::STRING);
var.value = array[i];
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_VECTOR2_ARRAY: {
int id = variable_id++;
PackedVector2Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = Variant::get_type_name(Variant::VECTOR2);
var.value = array[i];
var.variablesReference = parse_variant(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_VECTOR3_ARRAY: {
int id = variable_id++;
PackedVector2Array array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = Variant::get_type_name(Variant::VECTOR3);
var.value = array[i];
var.variablesReference = parse_variant(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
case Variant::PACKED_COLOR_ARRAY: {
int id = variable_id++;
PackedColorArray array = p_var;
DAP::Variable size;
size.name = "size";
size.type = Variant::get_type_name(Variant::INT);
size.value = itos(array.size());
Array arr;
arr.push_back(size.to_json());
for (int i = 0; i < array.size(); i++) {
DAP::Variable var;
var.name = itos(i);
var.type = Variant::get_type_name(Variant::COLOR);
var.value = array[i];
var.variablesReference = parse_variant(array[i]);
arr.push_back(var.to_json());
}
variable_list.insert(id, arr);
return id;
}
default:
// Simple atomic stuff, or too complex to be manipulated
return 0;
}
}
bool DebugAdapterProtocol::process_message(const String &p_text) {
JSON json;
ERR_FAIL_COND_V_MSG(json.parse(p_text) != OK, true, "Mal-formed message!");
Dictionary params = json.get_data();
bool completed = true;
if (OS::get_singleton()->get_ticks_msec() - _current_peer->timestamp > _request_timeout) {
Dictionary response = parser->prepare_error_response(params, DAP::ErrorType::TIMEOUT);
_current_peer->res_queue.push_front(response);
return true;
}
// Append "req_" to any command received; prevents name clash with existing functions, and possibly exploiting
String command = "req_" + (String)params["command"];
if (parser->has_method(command)) {
@ -211,7 +665,7 @@ void DebugAdapterProtocol::notify_initialized() {
}
void DebugAdapterProtocol::notify_process() {
String launch_mode = _current_request.is_empty() ? "launch" : _current_request;
String launch_mode = _current_peer->attached ? "attach" : "launch";
Dictionary event = parser->ev_process(launch_mode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
@ -222,7 +676,7 @@ void DebugAdapterProtocol::notify_process() {
void DebugAdapterProtocol::notify_terminated() {
Dictionary event = parser->ev_terminated();
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
if (_current_request == "launch" && _current_peer == E->get()) {
if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@ -232,7 +686,7 @@ void DebugAdapterProtocol::notify_terminated() {
void DebugAdapterProtocol::notify_exited(const int &p_exitcode) {
Dictionary event = parser->ev_exited(p_exitcode);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
if (_current_request == "launch" && _current_peer == E->get()) {
if ((_current_request == "launch" || _current_request == "restart") && _current_peer == E->get()) {
continue;
}
E->get()->res_queue.push_back(event);
@ -286,25 +740,46 @@ void DebugAdapterProtocol::notify_output(const String &p_message) {
}
}
void DebugAdapterProtocol::notify_custom_data(const String &p_msg, const Array &p_data) {
Dictionary event = parser->ev_custom_data(p_msg, p_data);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
Ref<DAPeer> peer = E->get();
if (peer->supportsCustomData) {
peer->res_queue.push_back(event);
}
}
}
void DebugAdapterProtocol::notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) {
Dictionary event = parser->ev_breakpoint(p_breakpoint, p_enabled);
for (List<Ref<DAPeer>>::Element *E = clients.front(); E; E = E->next()) {
if (_current_request == "setBreakpoints" && E->get() == _current_peer) {
continue;
}
E->get()->res_queue.push_back(event);
}
}
Array DebugAdapterProtocol::update_breakpoints(const String &p_path, const Array &p_lines) {
Array updated_breakpoints;
// Add breakpoints
for (int i = 0; i < p_lines.size(); i++) {
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, p_lines[i], true);
DAP::Breakpoint breakpoint;
breakpoint.verified = true;
breakpoint.source.path = p_path;
breakpoint.source.compute_checksums();
breakpoint.line = p_lines[i];
breakpoint.source.path = p_path;
List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
if (E) {
breakpoint.id = E->get().id;
} else {
breakpoint.id = breakpoint_id++;
breakpoint_list.push_back(breakpoint);
ERR_FAIL_COND_V(!breakpoint_list.find(breakpoint), Array());
updated_breakpoints.push_back(breakpoint_list.find(breakpoint)->get().to_json());
}
// Remove breakpoints
for (List<DAP::Breakpoint>::Element *E = breakpoint_list.front(); E; E = E->next()) {
DAP::Breakpoint b = E->get();
if (b.source.path == p_path && !p_lines.has(b.line)) {
EditorDebuggerNode::get_singleton()->get_default_debugger()->_set_breakpoint(p_path, b.line, false);
}
updated_breakpoints.push_back(breakpoint.to_json());
}
return updated_breakpoints;
@ -347,6 +822,29 @@ void DebugAdapterProtocol::on_debug_breaked(const bool &p_reallydid, const bool
_processing_stackdump = p_has_stackdump;
}
void DebugAdapterProtocol::on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled) {
DAP::Breakpoint breakpoint;
breakpoint.verified = true;
breakpoint.source.path = ProjectSettings::get_singleton()->globalize_path(p_path);
breakpoint.source.compute_checksums();
breakpoint.line = p_line;
if (p_enabled) {
// Add the breakpoint
breakpoint.id = breakpoint_id++;
breakpoint_list.push_back(breakpoint);
} else {
// Remove the breakpoint
List<DAP::Breakpoint>::Element *E = breakpoint_list.find(breakpoint);
if (E) {
breakpoint.id = E->get().id;
breakpoint_list.erase(E);
}
}
notify_breakpoint(breakpoint, p_enabled);
}
void DebugAdapterProtocol::on_debug_stack_dump(const Array &p_stack_dump) {
if (_processing_breakpoint && !p_stack_dump.is_empty()) {
// Find existing breakpoint
@ -424,11 +922,21 @@ void DebugAdapterProtocol::on_debug_stack_frame_var(const Array &p_data) {
variable.name = stack_var.name;
variable.value = stack_var.value;
variable.type = Variant::get_type_name(stack_var.value.get_type());
variable.variablesReference = parse_variant(stack_var.value);
variable_list.find(variable_id)->value().push_back(variable.to_json());
_remaining_vars--;
}
void DebugAdapterProtocol::on_debug_data(const String &p_msg, const Array &p_data) {
// Ignore data that is already handled by DAP
if (p_msg == "debug_enter" || p_msg == "debug_exit" || p_msg == "stack_dump" || p_msg == "stack_frame_vars" || p_msg == "stack_frame_var" || p_msg == "output" || p_msg == "request_quit") {
return;
}
notify_custom_data(p_msg, p_data);
}
void DebugAdapterProtocol::poll() {
if (server->is_connection_available()) {
on_client_connected();
@ -459,6 +967,8 @@ void DebugAdapterProtocol::poll() {
}
Error DebugAdapterProtocol::start(int p_port, const IPAddress &p_bind_ip) {
_request_timeout = (uint64_t)_EDITOR_GET("network/debug_adapter/request_timeout");
_sync_breakpoints = (bool)_EDITOR_GET("network/debug_adapter/sync_breakpoints");
_initialized = true;
return server->listen(p_port, p_bind_ip);
}
@ -484,12 +994,15 @@ DebugAdapterProtocol::DebugAdapterProtocol() {
node->get_pause_button()->connect("pressed", callable_mp(this, &DebugAdapterProtocol::on_debug_paused));
EditorDebuggerNode *debugger_node = EditorDebuggerNode::get_singleton();
debugger_node->connect("breakpoint_toggled", callable_mp(this, &DebugAdapterProtocol::on_debug_breakpoint_toggled));
debugger_node->get_default_debugger()->connect("stopped", callable_mp(this, &DebugAdapterProtocol::on_debug_stopped));
debugger_node->get_default_debugger()->connect("output", callable_mp(this, &DebugAdapterProtocol::on_debug_output));
debugger_node->get_default_debugger()->connect("breaked", callable_mp(this, &DebugAdapterProtocol::on_debug_breaked));
debugger_node->get_default_debugger()->connect("stack_dump", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_dump));
debugger_node->get_default_debugger()->connect("stack_frame_vars", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_vars));
debugger_node->get_default_debugger()->connect("stack_frame_var", callable_mp(this, &DebugAdapterProtocol::on_debug_stack_frame_var));
debugger_node->get_default_debugger()->connect("debug_data", callable_mp(this, &DebugAdapterProtocol::on_debug_data));
}
DebugAdapterProtocol::~DebugAdapterProtocol() {

View File

@ -52,12 +52,17 @@ struct DAPeer : RefCounted {
int content_length = 0;
List<Dictionary> res_queue;
int seq = 0;
uint64_t timestamp = 0;
// Client specific info
bool linesStartAt1 = false;
bool columnsStartAt1 = false;
bool supportsVariableType = false;
bool supportsInvalidatedEvent = false;
bool supportsCustomData = false;
// Internal client info
bool attached = false;
Error handle_data();
Error send_data();
@ -82,20 +87,26 @@ private:
void on_debug_stopped();
void on_debug_output(const String &p_message);
void on_debug_breaked(const bool &p_reallydid, const bool &p_can_debug, const String &p_reason, const bool &p_has_stackdump);
void on_debug_breakpoint_toggled(const String &p_path, const int &p_line, const bool &p_enabled);
void on_debug_stack_dump(const Array &p_stack_dump);
void on_debug_stack_frame_vars(const int &p_size);
void on_debug_stack_frame_var(const Array &p_data);
void on_debug_data(const String &p_msg, const Array &p_data);
void reset_current_info();
void reset_ids();
void reset_stack_info();
int parse_variant(const Variant &p_var);
bool _initialized = false;
bool _processing_breakpoint = false;
bool _stepping = false;
bool _processing_stackdump = false;
int _remaining_vars = 0;
int _current_frame = 0;
uint64_t _request_timeout = 1000;
bool _sync_breakpoints = false;
String _current_request;
Ref<DAPeer> _current_peer;
@ -108,6 +119,8 @@ private:
Map<int, Array> variable_list;
public:
friend class DebugAdapterServer;
_FORCE_INLINE_ static DebugAdapterProtocol *get_singleton() { return singleton; }
_FORCE_INLINE_ bool is_active() const { return _initialized && clients.size() > 0; }
@ -126,8 +139,10 @@ public:
void notify_stopped_step();
void notify_continued();
void notify_output(const String &p_message);
void notify_custom_data(const String &p_msg, const Array &p_data);
void notify_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled);
Array update_breakpoints(const String &p_path, const Array &p_breakpoints);
Array update_breakpoints(const String &p_path, const Array &p_lines);
void poll();
Error start(int p_port, const IPAddress &p_bind_ip);

View File

@ -36,7 +36,8 @@
DebugAdapterServer::DebugAdapterServer() {
_EDITOR_DEF("network/debug_adapter/remote_port", remote_port);
_EDITOR_DEF("network/debug_adapter/use_thread", use_thread);
_EDITOR_DEF("network/debug_adapter/request_timeout", protocol._request_timeout);
_EDITOR_DEF("network/debug_adapter/sync_breakpoints", protocol._sync_breakpoints);
}
void DebugAdapterServer::_notification(int p_what) {
@ -50,16 +51,17 @@ void DebugAdapterServer::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
// The main loop can be run again during request processing, which modifies internal state of the protocol.
// Thus, "polling" is needed to prevent it from parsing other requests while the current one isn't finished.
if (started && !use_thread && !polling) {
if (started && !polling) {
polling = true;
protocol.poll();
polling = false;
}
} break;
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
protocol._request_timeout = EditorSettings::get_singleton()->get("network/debug_adapter/request_timeout");
protocol._sync_breakpoints = EditorSettings::get_singleton()->get("network/debug_adapter/sync_breakpoints");
int remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
bool use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
if (remote_port != this->remote_port || use_thread != this->use_thread) {
if (remote_port != this->remote_port) {
this->stop();
this->start();
}
@ -67,35 +69,16 @@ void DebugAdapterServer::_notification(int p_what) {
}
}
void DebugAdapterServer::thread_func(void *p_userdata) {
DebugAdapterServer *self = static_cast<DebugAdapterServer *>(p_userdata);
while (self->thread_running) {
// Poll 20 times per second
self->protocol.poll();
OS::get_singleton()->delay_usec(50000);
}
}
void DebugAdapterServer::start() {
remote_port = (int)_EDITOR_GET("network/debug_adapter/remote_port");
use_thread = (bool)_EDITOR_GET("network/debug_adapter/use_thread");
if (protocol.start(remote_port, IPAddress("127.0.0.1")) == OK) {
EditorNode::get_log()->add_message("--- Debug adapter server started ---", EditorLog::MSG_TYPE_EDITOR);
if (use_thread) {
thread_running = true;
thread.start(DebugAdapterServer::thread_func, this);
}
set_process_internal(!use_thread);
set_process_internal(true);
started = true;
}
}
void DebugAdapterServer::stop() {
if (use_thread) {
ERR_FAIL_COND(!thread.is_started());
thread_running = false;
thread.wait_to_finish();
}
protocol.stop();
started = false;
EditorNode::get_log()->add_message("--- Debug adapter server stopped ---", EditorLog::MSG_TYPE_EDITOR);

View File

@ -39,11 +39,9 @@ class DebugAdapterServer : public EditorPlugin {
DebugAdapterProtocol protocol;
Thread thread;
int remote_port = 6006;
bool thread_running = false;
bool started = false;
bool use_thread = false;
bool polling = false;
static void thread_func(void *p_userdata);

View File

@ -38,7 +38,11 @@ namespace DAP {
enum ErrorType {
UNKNOWN,
WRONG_PATH
WRONG_PATH,
NOT_RUNNING,
TIMEOUT,
UNKNOWN_PLATFORM,
MISSING_DEVICE
};
struct Checksum {
@ -118,10 +122,14 @@ struct Breakpoint {
struct BreakpointLocation {
int line;
int endLine = -1;
_FORCE_INLINE_ Dictionary to_json() const {
Dictionary dict;
dict["line"] = line;
if (endLine >= 0) {
dict["endLine"] = endLine;
}
return dict;
}

View File

@ -164,6 +164,7 @@ void EditorDebuggerNode::_bind_methods() {
ADD_SIGNAL(MethodInfo("set_execution", PropertyInfo("script"), PropertyInfo(Variant::INT, "line")));
ADD_SIGNAL(MethodInfo("clear_execution", PropertyInfo("script")));
ADD_SIGNAL(MethodInfo("breaked", PropertyInfo(Variant::BOOL, "reallydid"), PropertyInfo(Variant::BOOL, "can_debug")));
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
}
EditorDebuggerRemoteObject *EditorDebuggerNode::get_inspected_remote_object() {
@ -487,6 +488,8 @@ void EditorDebuggerNode::set_breakpoint(const String &p_path, int p_line, bool p
_for_all(tabs, [&](ScriptEditorDebugger *dbg) {
dbg->set_breakpoint(p_path, p_line, p_enabled);
});
emit_signal("breakpoint_toggled", p_path, p_line, p_enabled);
}
void EditorDebuggerNode::set_breakpoints(const String &p_path, Array p_lines) {

View File

@ -296,6 +296,7 @@ Size2 ScriptEditorDebugger::get_minimum_size() const {
}
void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_data) {
emit_signal(SNAME("debug_data"), p_msg, p_data);
if (p_msg == "debug_enter") {
_put_msg("get_stack_dump", Array());
@ -872,6 +873,16 @@ void ScriptEditorDebugger::_clear_execution() {
inspector->clear_stack_variables();
}
void ScriptEditorDebugger::_set_breakpoint(const String &p_file, const int &p_line, const bool &p_enabled) {
Ref<Script> script = ResourceLoader::load(p_file);
emit_signal("set_breakpoint", script, p_line - 1, p_enabled);
script.unref();
}
void ScriptEditorDebugger::_clear_breakpoints() {
emit_signal("clear_breakpoints");
}
void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
error_count = 0;
warning_count = 0;
@ -1503,6 +1514,9 @@ void ScriptEditorDebugger::_bind_methods() {
ADD_SIGNAL(MethodInfo("stack_dump", PropertyInfo(Variant::ARRAY, "stack_dump")));
ADD_SIGNAL(MethodInfo("stack_frame_vars", PropertyInfo(Variant::INT, "num_vars")));
ADD_SIGNAL(MethodInfo("stack_frame_var", PropertyInfo(Variant::ARRAY, "data")));
ADD_SIGNAL(MethodInfo("debug_data", PropertyInfo(Variant::STRING, "msg"), PropertyInfo(Variant::ARRAY, "data")));
ADD_SIGNAL(MethodInfo("set_breakpoint", PropertyInfo("script"), PropertyInfo(Variant::INT, "line"), PropertyInfo(Variant::BOOL, "enabled")));
ADD_SIGNAL(MethodInfo("clear_breakpoints"));
}
void ScriptEditorDebugger::add_debugger_plugin(const Ref<Script> &p_script) {

View File

@ -205,6 +205,9 @@ private:
void _clear_execution();
void _stop_and_notify();
void _set_breakpoint(const String &p_path, const int &p_line, const bool &p_enabled);
void _clear_breakpoints();
protected:
void _notification(int p_what);
static void _bind_methods();

View File

@ -4759,6 +4759,10 @@ bool EditorNode::ensure_main_scene(bool p_from_native) {
return true;
}
Error EditorNode::run_play_native(int p_idx, int p_platform) {
return run_native->run_native(p_idx, p_platform);
}
void EditorNode::run_play() {
_menu_option_confirm(RUN_STOP, true);
_run(false);

View File

@ -893,6 +893,7 @@ public:
bool ensure_main_scene(bool p_from_native);
Error run_play_native(int p_idx, int p_platform);
void run_play();
void run_play_current();
void run_play_custom(const String &p_custom);

View File

@ -52,8 +52,8 @@ void EditorRunNative::_notification(int p_what) {
small_icon.instantiate();
small_icon->create_from_image(im);
MenuButton *mb = memnew(MenuButton);
mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::_run_native), varray(i));
mb->connect("pressed", callable_mp(this, &EditorRunNative::_run_native), varray(-1, i));
mb->get_popup()->connect("id_pressed", callable_mp(this, &EditorRunNative::run_native), varray(i));
mb->connect("pressed", callable_mp(this, &EditorRunNative::run_native), varray(-1, i));
mb->set_icon(small_icon);
add_child(mb);
menus[i] = mb;
@ -93,22 +93,22 @@ void EditorRunNative::_notification(int p_what) {
}
}
void EditorRunNative::_run_native(int p_idx, int p_platform) {
Error EditorRunNative::run_native(int p_idx, int p_platform) {
if (!EditorNode::get_singleton()->ensure_main_scene(true)) {
resume_idx = p_idx;
resume_platform = p_platform;
return;
return OK;
}
Ref<EditorExportPlatform> eep = EditorExport::get_singleton()->get_export_platform(p_platform);
ERR_FAIL_COND(eep.is_null());
ERR_FAIL_COND_V(eep.is_null(), ERR_UNAVAILABLE);
if (p_idx == -1) {
if (eep->get_options_count() == 1) {
menus[p_platform]->get_popup()->hide();
p_idx = 0;
} else {
return;
return ERR_INVALID_PARAMETER;
}
}
@ -124,7 +124,7 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
if (preset.is_null()) {
EditorNode::get_singleton()->show_warning(TTR("No runnable export preset found for this platform.\nPlease add a runnable preset in the Export menu or define an existing preset as runnable."));
return;
return ERR_UNAVAILABLE;
}
emit_signal(SNAME("native_run"), preset);
@ -149,11 +149,11 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) {
flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION;
}
eep->run(preset, p_idx, flags);
return eep->run(preset, p_idx, flags);
}
void EditorRunNative::resume_run_native() {
_run_native(resume_idx, resume_platform);
run_native(resume_idx, resume_platform);
}
void EditorRunNative::_bind_methods() {

View File

@ -43,13 +43,12 @@ class EditorRunNative : public HBoxContainer {
int resume_idx;
int resume_platform;
void _run_native(int p_idx, int p_platform);
protected:
static void _bind_methods();
void _notification(int p_what);
public:
Error run_native(int p_idx, int p_platform);
bool is_deploy_debug_remote_enabled() const;
void resume_run_native();

View File

@ -37,6 +37,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/debugger/script_editor_debugger.h"
#include "editor/editor_node.h"
#include "editor/editor_run_script.h"
#include "editor/editor_scale.h"
@ -486,6 +487,29 @@ void ScriptEditor::_clear_execution(REF p_script) {
}
}
void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) {
Ref<Script> script = Object::cast_to<Script>(*p_script);
if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) {
if (edit(p_script, p_line, 0, false)) {
editor->push_item(p_script.ptr());
ScriptEditorBase *se = _get_current_editor();
if (se) {
se->set_breakpoint(p_line, p_enabled);
}
}
}
}
void ScriptEditor::_clear_breakpoints() {
for (int i = 0; i < tab_container->get_child_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
if (se) {
se->clear_breakpoints();
}
}
}
ScriptEditorBase *ScriptEditor::_get_current_editor() const {
int selected = tab_container->get_current_tab();
if (selected < 0 || selected >= tab_container->get_child_count()) {
@ -3488,6 +3512,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution));
debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution));
debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked));
debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint));
debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints));
menu_hb->add_spacer();

View File

@ -152,6 +152,8 @@ public:
virtual void tag_saved_version() = 0;
virtual void reload(bool p_soft) {}
virtual Array get_breakpoints() = 0;
virtual void set_breakpoint(int p_line, bool p_enabled) = 0;
virtual void clear_breakpoints() = 0;
virtual void add_callback(const String &p_function, PackedStringArray p_args) = 0;
virtual void update_settings() = 0;
virtual void set_debugger_active(bool p_active) = 0;
@ -370,6 +372,8 @@ class ScriptEditor : public PanelContainer {
void _breaked(bool p_breaked, bool p_can_debug);
void _update_window_menu();
void _script_created(Ref<Script> p_script);
void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled);
void _clear_breakpoints();
ScriptEditorBase *_get_current_editor() const;
Array _get_open_script_editors() const;

View File

@ -1370,6 +1370,14 @@ Array ScriptTextEditor::get_breakpoints() {
return code_editor->get_text_editor()->get_breakpointed_lines();
}
void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) {
code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled);
}
void ScriptTextEditor::clear_breakpoints() {
code_editor->get_text_editor()->clear_breakpointed_lines();
}
void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) {
code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this);
}

View File

@ -224,6 +224,8 @@ public:
virtual void reload(bool p_soft) override;
virtual Array get_breakpoints() override;
virtual void set_breakpoint(int p_line, bool p_enabled) override;
virtual void clear_breakpoints() override;
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;

View File

@ -120,6 +120,8 @@ public:
virtual void set_edit_state(const Variant &p_state) override;
virtual Vector<String> get_functions() override;
virtual Array get_breakpoints() override;
virtual void set_breakpoint(int p_line, bool p_enabled) override{};
virtual void clear_breakpoints() override{};
virtual void goto_line(int p_line, bool p_with_error = false) override;
void goto_line_selection(int p_line, int p_begin, int p_end);
virtual void set_executing_line(int p_line) override;

View File

@ -311,6 +311,8 @@ public:
virtual void tag_saved_version() override;
virtual void reload(bool p_soft) override;
virtual Array get_breakpoints() override;
virtual void set_breakpoint(int p_line, bool p_enable) override{};
virtual void clear_breakpoints() override{};
virtual void add_callback(const String &p_function, PackedStringArray p_args) override;
virtual void update_settings() override;
virtual bool show_members_overview() override;