1 | /**************************************************************************/ |
2 | /* debug_adapter_parser.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 "debug_adapter_parser.h" |
32 | |
33 | #include "editor/debugger/editor_debugger_node.h" |
34 | #include "editor/debugger/script_editor_debugger.h" |
35 | #include "editor/export/editor_export_platform.h" |
36 | #include "editor/gui/editor_run_bar.h" |
37 | #include "editor/plugins/script_editor_plugin.h" |
38 | |
39 | void DebugAdapterParser::_bind_methods() { |
40 | // Requests |
41 | ClassDB::bind_method(D_METHOD("req_initialize" , "params" ), &DebugAdapterParser::req_initialize); |
42 | ClassDB::bind_method(D_METHOD("req_disconnect" , "params" ), &DebugAdapterParser::req_disconnect); |
43 | ClassDB::bind_method(D_METHOD("req_launch" , "params" ), &DebugAdapterParser::req_launch); |
44 | ClassDB::bind_method(D_METHOD("req_attach" , "params" ), &DebugAdapterParser::req_attach); |
45 | ClassDB::bind_method(D_METHOD("req_restart" , "params" ), &DebugAdapterParser::req_restart); |
46 | ClassDB::bind_method(D_METHOD("req_terminate" , "params" ), &DebugAdapterParser::req_terminate); |
47 | ClassDB::bind_method(D_METHOD("req_configurationDone" , "params" ), &DebugAdapterParser::prepare_success_response); |
48 | ClassDB::bind_method(D_METHOD("req_pause" , "params" ), &DebugAdapterParser::req_pause); |
49 | ClassDB::bind_method(D_METHOD("req_continue" , "params" ), &DebugAdapterParser::req_continue); |
50 | ClassDB::bind_method(D_METHOD("req_threads" , "params" ), &DebugAdapterParser::req_threads); |
51 | ClassDB::bind_method(D_METHOD("req_stackTrace" , "params" ), &DebugAdapterParser::req_stackTrace); |
52 | ClassDB::bind_method(D_METHOD("req_setBreakpoints" , "params" ), &DebugAdapterParser::req_setBreakpoints); |
53 | ClassDB::bind_method(D_METHOD("req_breakpointLocations" , "params" ), &DebugAdapterParser::req_breakpointLocations); |
54 | ClassDB::bind_method(D_METHOD("req_scopes" , "params" ), &DebugAdapterParser::req_scopes); |
55 | ClassDB::bind_method(D_METHOD("req_variables" , "params" ), &DebugAdapterParser::req_variables); |
56 | ClassDB::bind_method(D_METHOD("req_next" , "params" ), &DebugAdapterParser::req_next); |
57 | ClassDB::bind_method(D_METHOD("req_stepIn" , "params" ), &DebugAdapterParser::req_stepIn); |
58 | ClassDB::bind_method(D_METHOD("req_evaluate" , "params" ), &DebugAdapterParser::req_evaluate); |
59 | ClassDB::bind_method(D_METHOD("req_godot/put_msg" , "params" ), &DebugAdapterParser::req_godot_put_msg); |
60 | } |
61 | |
62 | Dictionary DebugAdapterParser::prepare_base_event() const { |
63 | Dictionary event; |
64 | event["type" ] = "event" ; |
65 | |
66 | return event; |
67 | } |
68 | |
69 | Dictionary DebugAdapterParser::prepare_success_response(const Dictionary &p_params) const { |
70 | Dictionary response; |
71 | response["type" ] = "response" ; |
72 | response["request_seq" ] = p_params["seq" ]; |
73 | response["command" ] = p_params["command" ]; |
74 | response["success" ] = true; |
75 | |
76 | return response; |
77 | } |
78 | |
79 | Dictionary DebugAdapterParser::prepare_error_response(const Dictionary &p_params, DAP::ErrorType err_type, const Dictionary &variables) const { |
80 | Dictionary response, body; |
81 | response["type" ] = "response" ; |
82 | response["request_seq" ] = p_params["seq" ]; |
83 | response["command" ] = p_params["command" ]; |
84 | response["success" ] = false; |
85 | response["body" ] = body; |
86 | |
87 | DAP::Message message; |
88 | String error, error_desc; |
89 | switch (err_type) { |
90 | case DAP::ErrorType::WRONG_PATH: |
91 | error = "wrong_path" ; |
92 | error_desc = "The editor and client are working on different paths; the client is on \"{clientPath}\", but the editor is on \"{editorPath}\"" ; |
93 | break; |
94 | case DAP::ErrorType::NOT_RUNNING: |
95 | error = "not_running" ; |
96 | error_desc = "Can't attach to a running session since there isn't one." ; |
97 | break; |
98 | case DAP::ErrorType::TIMEOUT: |
99 | error = "timeout" ; |
100 | error_desc = "Timeout reached while processing a request." ; |
101 | break; |
102 | case DAP::ErrorType::UNKNOWN_PLATFORM: |
103 | error = "unknown_platform" ; |
104 | error_desc = "The specified platform is unknown." ; |
105 | break; |
106 | case DAP::ErrorType::MISSING_DEVICE: |
107 | error = "missing_device" ; |
108 | error_desc = "There's no connected device with specified id." ; |
109 | break; |
110 | case DAP::ErrorType::UNKNOWN: |
111 | default: |
112 | error = "unknown" ; |
113 | error_desc = "An unknown error has occurred when processing the request." ; |
114 | break; |
115 | } |
116 | |
117 | message.id = err_type; |
118 | message.format = error_desc; |
119 | message.variables = variables; |
120 | response["message" ] = error; |
121 | body["error" ] = message.to_json(); |
122 | |
123 | return response; |
124 | } |
125 | |
126 | Dictionary DebugAdapterParser::req_initialize(const Dictionary &p_params) const { |
127 | Dictionary response = prepare_success_response(p_params); |
128 | Dictionary args = p_params["arguments" ]; |
129 | |
130 | Ref<DAPeer> peer = DebugAdapterProtocol::get_singleton()->get_current_peer(); |
131 | |
132 | peer->linesStartAt1 = args.get("linesStartAt1" , false); |
133 | peer->columnsStartAt1 = args.get("columnsStartAt1" , false); |
134 | peer->supportsVariableType = args.get("supportsVariableType" , false); |
135 | peer->supportsInvalidatedEvent = args.get("supportsInvalidatedEvent" , false); |
136 | |
137 | DAP::Capabilities caps; |
138 | response["body" ] = caps.to_json(); |
139 | |
140 | DebugAdapterProtocol::get_singleton()->notify_initialized(); |
141 | |
142 | if (DebugAdapterProtocol::get_singleton()->_sync_breakpoints) { |
143 | // Send all current breakpoints |
144 | List<String> breakpoints; |
145 | ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); |
146 | for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { |
147 | String breakpoint = E->get(); |
148 | |
149 | String path = breakpoint.left(breakpoint.find(":" , 6)); // Skip initial part of path, aka "res://" |
150 | int line = breakpoint.substr(path.size()).to_int(); |
151 | |
152 | DebugAdapterProtocol::get_singleton()->on_debug_breakpoint_toggled(path, line, true); |
153 | } |
154 | } else { |
155 | // Remove all current breakpoints |
156 | EditorDebuggerNode::get_singleton()->get_default_debugger()->_clear_breakpoints(); |
157 | } |
158 | |
159 | return response; |
160 | } |
161 | |
162 | Dictionary DebugAdapterParser::req_disconnect(const Dictionary &p_params) const { |
163 | if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->attached) { |
164 | EditorRunBar::get_singleton()->stop_playing(); |
165 | } |
166 | |
167 | return prepare_success_response(p_params); |
168 | } |
169 | |
170 | Dictionary DebugAdapterParser::req_launch(const Dictionary &p_params) const { |
171 | Dictionary args = p_params["arguments" ]; |
172 | if (args.has("project" ) && !is_valid_path(args["project" ])) { |
173 | Dictionary variables; |
174 | variables["clientPath" ] = args["project" ]; |
175 | variables["editorPath" ] = ProjectSettings::get_singleton()->get_resource_path(); |
176 | return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); |
177 | } |
178 | |
179 | if (args.has("godot/custom_data" )) { |
180 | DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsCustomData = args["godot/custom_data" ]; |
181 | } |
182 | |
183 | ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); |
184 | if ((bool)args["noDebug" ] != dbg->is_skip_breakpoints()) { |
185 | dbg->debug_skip_breakpoints(); |
186 | } |
187 | |
188 | String platform_string = args.get("platform" , "host" ); |
189 | if (platform_string == "host" ) { |
190 | EditorRunBar::get_singleton()->play_main_scene(); |
191 | } else { |
192 | int device = args.get("device" , -1); |
193 | int idx = -1; |
194 | if (platform_string == "android" ) { |
195 | for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { |
196 | if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Android" ) { |
197 | idx = i; |
198 | break; |
199 | } |
200 | } |
201 | } else if (platform_string == "web" ) { |
202 | for (int i = 0; i < EditorExport::get_singleton()->get_export_platform_count(); i++) { |
203 | if (EditorExport::get_singleton()->get_export_platform(i)->get_name() == "Web" ) { |
204 | idx = i; |
205 | break; |
206 | } |
207 | } |
208 | } |
209 | |
210 | if (idx == -1) { |
211 | return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN_PLATFORM); |
212 | } |
213 | |
214 | EditorRunBar *run_bar = EditorRunBar::get_singleton(); |
215 | Error err = platform_string == "android" ? run_bar->start_native_device(device * 10000 + idx) : run_bar->start_native_device(idx); |
216 | if (err) { |
217 | if (err == ERR_INVALID_PARAMETER && platform_string == "android" ) { |
218 | return prepare_error_response(p_params, DAP::ErrorType::MISSING_DEVICE); |
219 | } else { |
220 | return prepare_error_response(p_params, DAP::ErrorType::UNKNOWN); |
221 | } |
222 | } |
223 | } |
224 | |
225 | DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = false; |
226 | DebugAdapterProtocol::get_singleton()->notify_process(); |
227 | |
228 | return prepare_success_response(p_params); |
229 | } |
230 | |
231 | Dictionary DebugAdapterParser::req_attach(const Dictionary &p_params) const { |
232 | ScriptEditorDebugger *dbg = EditorDebuggerNode::get_singleton()->get_default_debugger(); |
233 | if (!dbg->is_session_active()) { |
234 | return prepare_error_response(p_params, DAP::ErrorType::NOT_RUNNING); |
235 | } |
236 | |
237 | DebugAdapterProtocol::get_singleton()->get_current_peer()->attached = true; |
238 | DebugAdapterProtocol::get_singleton()->notify_process(); |
239 | return prepare_success_response(p_params); |
240 | } |
241 | |
242 | Dictionary DebugAdapterParser::req_restart(const Dictionary &p_params) const { |
243 | // Extract embedded "arguments" so it can be given to req_launch/req_attach |
244 | Dictionary params = p_params, args; |
245 | args = params["arguments" ]; |
246 | args = args["arguments" ]; |
247 | params["arguments" ] = args; |
248 | |
249 | Dictionary response = DebugAdapterProtocol::get_singleton()->get_current_peer()->attached ? req_attach(params) : req_launch(params); |
250 | if (!response["success" ]) { |
251 | response["command" ] = p_params["command" ]; |
252 | return response; |
253 | } |
254 | |
255 | return prepare_success_response(p_params); |
256 | } |
257 | |
258 | Dictionary DebugAdapterParser::req_terminate(const Dictionary &p_params) const { |
259 | EditorRunBar::get_singleton()->stop_playing(); |
260 | |
261 | return prepare_success_response(p_params); |
262 | } |
263 | |
264 | Dictionary DebugAdapterParser::req_pause(const Dictionary &p_params) const { |
265 | EditorRunBar::get_singleton()->get_pause_button()->set_pressed(true); |
266 | EditorDebuggerNode::get_singleton()->_paused(); |
267 | |
268 | DebugAdapterProtocol::get_singleton()->notify_stopped_paused(); |
269 | |
270 | return prepare_success_response(p_params); |
271 | } |
272 | |
273 | Dictionary DebugAdapterParser::req_continue(const Dictionary &p_params) const { |
274 | EditorRunBar::get_singleton()->get_pause_button()->set_pressed(false); |
275 | EditorDebuggerNode::get_singleton()->_paused(); |
276 | |
277 | DebugAdapterProtocol::get_singleton()->notify_continued(); |
278 | |
279 | return prepare_success_response(p_params); |
280 | } |
281 | |
282 | Dictionary DebugAdapterParser::req_threads(const Dictionary &p_params) const { |
283 | Dictionary response = prepare_success_response(p_params), body; |
284 | response["body" ] = body; |
285 | |
286 | Array arr; |
287 | DAP::Thread thread; |
288 | |
289 | thread.id = 1; // Hardcoded because Godot only supports debugging one thread at the moment |
290 | thread.name = "Main" ; |
291 | arr.push_back(thread.to_json()); |
292 | body["threads" ] = arr; |
293 | |
294 | return response; |
295 | } |
296 | |
297 | Dictionary DebugAdapterParser::req_stackTrace(const Dictionary &p_params) const { |
298 | if (DebugAdapterProtocol::get_singleton()->_processing_stackdump) { |
299 | return Dictionary(); |
300 | } |
301 | |
302 | Dictionary response = prepare_success_response(p_params), body; |
303 | response["body" ] = body; |
304 | |
305 | bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; |
306 | bool columns_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->columnsStartAt1; |
307 | |
308 | Array arr; |
309 | DebugAdapterProtocol *dap = DebugAdapterProtocol::get_singleton(); |
310 | for (const KeyValue<DAP::StackFrame, List<int>> &E : dap->stackframe_list) { |
311 | DAP::StackFrame sf = E.key; |
312 | if (!lines_at_one) { |
313 | sf.line--; |
314 | } |
315 | if (!columns_at_one) { |
316 | sf.column--; |
317 | } |
318 | |
319 | arr.push_back(sf.to_json()); |
320 | } |
321 | |
322 | body["stackFrames" ] = arr; |
323 | return response; |
324 | } |
325 | |
326 | Dictionary DebugAdapterParser::req_setBreakpoints(const Dictionary &p_params) const { |
327 | Dictionary response = prepare_success_response(p_params), body; |
328 | response["body" ] = body; |
329 | |
330 | Dictionary args = p_params["arguments" ]; |
331 | DAP::Source source; |
332 | source.from_json(args["source" ]); |
333 | |
334 | bool lines_at_one = DebugAdapterProtocol::get_singleton()->get_current_peer()->linesStartAt1; |
335 | |
336 | if (!is_valid_path(source.path)) { |
337 | Dictionary variables; |
338 | variables["clientPath" ] = source.path; |
339 | variables["editorPath" ] = ProjectSettings::get_singleton()->get_resource_path(); |
340 | return prepare_error_response(p_params, DAP::ErrorType::WRONG_PATH, variables); |
341 | } |
342 | |
343 | // If path contains \, it's a Windows path, so we need to convert it to /, and make the drive letter uppercase |
344 | if (source.path.find("\\" ) != -1) { |
345 | source.path = source.path.replace("\\" , "/" ); |
346 | source.path = source.path.substr(0, 1).to_upper() + source.path.substr(1); |
347 | } |
348 | |
349 | Array breakpoints = args["breakpoints" ], lines; |
350 | for (int i = 0; i < breakpoints.size(); i++) { |
351 | DAP::SourceBreakpoint breakpoint; |
352 | breakpoint.from_json(breakpoints[i]); |
353 | |
354 | lines.push_back(breakpoint.line + !lines_at_one); |
355 | } |
356 | |
357 | Array updated_breakpoints = DebugAdapterProtocol::get_singleton()->update_breakpoints(source.path, lines); |
358 | body["breakpoints" ] = updated_breakpoints; |
359 | |
360 | return response; |
361 | } |
362 | |
363 | Dictionary DebugAdapterParser::req_breakpointLocations(const Dictionary &p_params) const { |
364 | Dictionary response = prepare_success_response(p_params), body; |
365 | response["body" ] = body; |
366 | Dictionary args = p_params["arguments" ]; |
367 | |
368 | Array locations; |
369 | DAP::BreakpointLocation location; |
370 | location.line = args["line" ]; |
371 | if (args.has("endLine" )) { |
372 | location.endLine = args["endLine" ]; |
373 | } |
374 | locations.push_back(location.to_json()); |
375 | |
376 | body["breakpoints" ] = locations; |
377 | return response; |
378 | } |
379 | |
380 | Dictionary DebugAdapterParser::req_scopes(const Dictionary &p_params) const { |
381 | Dictionary response = prepare_success_response(p_params), body; |
382 | response["body" ] = body; |
383 | |
384 | Dictionary args = p_params["arguments" ]; |
385 | int frame_id = args["frameId" ]; |
386 | Array scope_list; |
387 | |
388 | DAP::StackFrame frame; |
389 | frame.id = frame_id; |
390 | HashMap<DAP::StackFrame, List<int>, DAP::StackFrame>::Iterator E = DebugAdapterProtocol::get_singleton()->stackframe_list.find(frame); |
391 | if (E) { |
392 | ERR_FAIL_COND_V(E->value.size() != 3, prepare_error_response(p_params, DAP::ErrorType::UNKNOWN)); |
393 | for (int i = 0; i < 3; i++) { |
394 | DAP::Scope scope; |
395 | scope.variablesReference = E->value[i]; |
396 | switch (i) { |
397 | case 0: |
398 | scope.name = "Locals" ; |
399 | scope.presentationHint = "locals" ; |
400 | break; |
401 | case 1: |
402 | scope.name = "Members" ; |
403 | scope.presentationHint = "members" ; |
404 | break; |
405 | case 2: |
406 | scope.name = "Globals" ; |
407 | scope.presentationHint = "globals" ; |
408 | } |
409 | |
410 | scope_list.push_back(scope.to_json()); |
411 | } |
412 | } |
413 | |
414 | EditorDebuggerNode::get_singleton()->get_default_debugger()->request_stack_dump(frame_id); |
415 | DebugAdapterProtocol::get_singleton()->_current_frame = frame_id; |
416 | |
417 | body["scopes" ] = scope_list; |
418 | return response; |
419 | } |
420 | |
421 | Dictionary DebugAdapterParser::req_variables(const Dictionary &p_params) const { |
422 | // If _remaining_vars > 0, the debuggee is still sending a stack dump to the editor. |
423 | if (DebugAdapterProtocol::get_singleton()->_remaining_vars > 0) { |
424 | return Dictionary(); |
425 | } |
426 | |
427 | Dictionary response = prepare_success_response(p_params), body; |
428 | response["body" ] = body; |
429 | |
430 | Dictionary args = p_params["arguments" ]; |
431 | int variable_id = args["variablesReference" ]; |
432 | |
433 | HashMap<int, Array>::Iterator E = DebugAdapterProtocol::get_singleton()->variable_list.find(variable_id); |
434 | |
435 | if (E) { |
436 | if (!DebugAdapterProtocol::get_singleton()->get_current_peer()->supportsVariableType) { |
437 | for (int i = 0; i < E->value.size(); i++) { |
438 | Dictionary variable = E->value[i]; |
439 | variable.erase("type" ); |
440 | } |
441 | } |
442 | body["variables" ] = E ? E->value : Array(); |
443 | return response; |
444 | } else { |
445 | return Dictionary(); |
446 | } |
447 | } |
448 | |
449 | Dictionary DebugAdapterParser::req_next(const Dictionary &p_params) const { |
450 | EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_next(); |
451 | DebugAdapterProtocol::get_singleton()->_stepping = true; |
452 | |
453 | return prepare_success_response(p_params); |
454 | } |
455 | |
456 | Dictionary DebugAdapterParser::req_stepIn(const Dictionary &p_params) const { |
457 | EditorDebuggerNode::get_singleton()->get_default_debugger()->debug_step(); |
458 | DebugAdapterProtocol::get_singleton()->_stepping = true; |
459 | |
460 | return prepare_success_response(p_params); |
461 | } |
462 | |
463 | Dictionary DebugAdapterParser::req_evaluate(const Dictionary &p_params) const { |
464 | Dictionary response = prepare_success_response(p_params), body; |
465 | response["body" ] = body; |
466 | |
467 | Dictionary args = p_params["arguments" ]; |
468 | |
469 | String value = EditorDebuggerNode::get_singleton()->get_var_value(args["expression" ]); |
470 | body["result" ] = value; |
471 | |
472 | return response; |
473 | } |
474 | |
475 | Dictionary DebugAdapterParser::req_godot_put_msg(const Dictionary &p_params) const { |
476 | Dictionary args = p_params["arguments" ]; |
477 | |
478 | String msg = args["message" ]; |
479 | Array data = args["data" ]; |
480 | |
481 | EditorDebuggerNode::get_singleton()->get_default_debugger()->_put_msg(msg, data); |
482 | |
483 | return prepare_success_response(p_params); |
484 | } |
485 | |
486 | Dictionary DebugAdapterParser::ev_initialized() const { |
487 | Dictionary event = prepare_base_event(); |
488 | event["event" ] = "initialized" ; |
489 | |
490 | return event; |
491 | } |
492 | |
493 | Dictionary DebugAdapterParser::ev_process(const String &p_command) const { |
494 | Dictionary event = prepare_base_event(), body; |
495 | event["event" ] = "process" ; |
496 | event["body" ] = body; |
497 | |
498 | body["name" ] = OS::get_singleton()->get_executable_path(); |
499 | body["startMethod" ] = p_command; |
500 | |
501 | return event; |
502 | } |
503 | |
504 | Dictionary DebugAdapterParser::ev_terminated() const { |
505 | Dictionary event = prepare_base_event(); |
506 | event["event" ] = "terminated" ; |
507 | |
508 | return event; |
509 | } |
510 | |
511 | Dictionary DebugAdapterParser::ev_exited(const int &p_exitcode) const { |
512 | Dictionary event = prepare_base_event(), body; |
513 | event["event" ] = "exited" ; |
514 | event["body" ] = body; |
515 | |
516 | body["exitCode" ] = p_exitcode; |
517 | |
518 | return event; |
519 | } |
520 | |
521 | Dictionary DebugAdapterParser::ev_stopped() const { |
522 | Dictionary event = prepare_base_event(), body; |
523 | event["event" ] = "stopped" ; |
524 | event["body" ] = body; |
525 | |
526 | body["threadId" ] = 1; |
527 | |
528 | return event; |
529 | } |
530 | |
531 | Dictionary DebugAdapterParser::ev_stopped_paused() const { |
532 | Dictionary event = ev_stopped(); |
533 | Dictionary body = event["body" ]; |
534 | |
535 | body["reason" ] = "paused" ; |
536 | body["description" ] = "Paused" ; |
537 | |
538 | return event; |
539 | } |
540 | |
541 | Dictionary DebugAdapterParser::ev_stopped_exception(const String &p_error) const { |
542 | Dictionary event = ev_stopped(); |
543 | Dictionary body = event["body" ]; |
544 | |
545 | body["reason" ] = "exception" ; |
546 | body["description" ] = "Exception" ; |
547 | body["text" ] = p_error; |
548 | |
549 | return event; |
550 | } |
551 | |
552 | Dictionary DebugAdapterParser::ev_stopped_breakpoint(const int &p_id) const { |
553 | Dictionary event = ev_stopped(); |
554 | Dictionary body = event["body" ]; |
555 | |
556 | body["reason" ] = "breakpoint" ; |
557 | body["description" ] = "Breakpoint" ; |
558 | |
559 | Array breakpoints; |
560 | breakpoints.push_back(p_id); |
561 | body["hitBreakpointIds" ] = breakpoints; |
562 | |
563 | return event; |
564 | } |
565 | |
566 | Dictionary DebugAdapterParser::ev_stopped_step() const { |
567 | Dictionary event = ev_stopped(); |
568 | Dictionary body = event["body" ]; |
569 | |
570 | body["reason" ] = "step" ; |
571 | body["description" ] = "Breakpoint" ; |
572 | |
573 | return event; |
574 | } |
575 | |
576 | Dictionary DebugAdapterParser::ev_continued() const { |
577 | Dictionary event = prepare_base_event(), body; |
578 | event["event" ] = "continued" ; |
579 | event["body" ] = body; |
580 | |
581 | body["threadId" ] = 1; |
582 | |
583 | return event; |
584 | } |
585 | |
586 | Dictionary DebugAdapterParser::ev_output(const String &p_message) const { |
587 | Dictionary event = prepare_base_event(), body; |
588 | event["event" ] = "output" ; |
589 | event["body" ] = body; |
590 | |
591 | body["category" ] = "stdout" ; |
592 | body["output" ] = p_message + "\r\n" ; |
593 | |
594 | return event; |
595 | } |
596 | |
597 | Dictionary DebugAdapterParser::ev_breakpoint(const DAP::Breakpoint &p_breakpoint, const bool &p_enabled) const { |
598 | Dictionary event = prepare_base_event(), body; |
599 | event["event" ] = "breakpoint" ; |
600 | event["body" ] = body; |
601 | |
602 | body["reason" ] = p_enabled ? "new" : "removed" ; |
603 | body["breakpoint" ] = p_breakpoint.to_json(); |
604 | |
605 | return event; |
606 | } |
607 | |
608 | Dictionary DebugAdapterParser::ev_custom_data(const String &p_msg, const Array &p_data) const { |
609 | Dictionary event = prepare_base_event(), body; |
610 | event["event" ] = "godot/custom_data" ; |
611 | event["body" ] = body; |
612 | |
613 | body["message" ] = p_msg; |
614 | body["data" ] = p_data; |
615 | |
616 | return event; |
617 | } |
618 | |